From: Stuart Prescott Date: Sat, 16 Mar 2024 01:11:38 +0000 (+1100) Subject: New upstream version 6.6.2 X-Git-Tag: archive/raspbian/6.10.3-2+rpi1~1^2~128^2 X-Git-Url: https://dgit.raspbian.org/%22http://www.example.com/cgi/%22/%22http:/www.example.com/cgi/%22?a=commitdiff_plain;h=e1f83a75aa19f2cd66000c294d6d213d87a080e6;p=pyside6.git New upstream version 6.6.2 --- diff --git a/.QT-ENTERPRISE-LICENSE-AGREEMENT b/.QT-ENTERPRISE-LICENSE-AGREEMENT index ff775f95..2d225ecf 100644 --- a/.QT-ENTERPRISE-LICENSE-AGREEMENT +++ b/.QT-ENTERPRISE-LICENSE-AGREEMENT @@ -1,2091 +1,262 @@ -QT LICENSE AGREEMENT -Agreement version 4.4.1 - -This Qt License Agreement ("Agreement") is a legal agreement for the licensing -of Licensed Software (as defined below) between The Qt Company (as defined -below) and the Licensee who has accepted the terms of this Agreement by signing -this Agreement or by downloading or using the Licensed Software or in any other -appropriate means. - -Capitalized terms used herein are defined in Section 1. - -WHEREAS: - (A) Licensee wishes to use the Licensed Software for the purpose of - developing and distributing Applications and/or Devices (each as defined - below); - (B) The Qt Company is willing to grant the Licensee a right to use Licensed - Software for such a purpose pursuant to term and conditions of this - Agreement; and - (C) Parties wish to enable that their respective Affiliates also can sell - and purchase licenses to serve Licensee Affiliates' needs to use Licensed - Software pursuant to terms of the Agreement. Any such license purchases by - Licensee Affiliates from The Qt Company or its Affiliates will create - contractual relationship directly between the relevant The Qt Company and - the respective ordering Licensee Affiliate "Acceding Agreement"). - Accordingly, Licensee shall not be a party to any such Acceding Agreement, - and no rights or obligations are created to the Licensee thereunder but all - rights and obligations under such Acceding Agreement are vested and borne - solely by the ordering Licensee Affiliate and the relevant The Qt Company - as a contracting parties under such Acceding Agreement. - -NOW, THEREFORE, THE PARTIES HEREBY AGREE AS FOLLOWS: - -1. DEFINITIONS - -"Affiliate" of a Party shall mean an entity - (i) which is directly or indirectly controlling such Party; - (ii) which is under the same direct or indirect ownership or control as - such Party; or - (iii) which is directly or indirectly owned or controlled by such Party. -For these purposes, an entity shall be treated as being controlled by another -if that other entity has fifty percent (50 %) or more of the votes in such -entity, is able to direct its affairs and/or to control the composition of its -board of directors or equivalent body. - -"Add-on Products" shall mean The Qt Company's specific add-on software products -which are not licensed as part of The Qt Company's standard product offering, -but shall be included into the scope of Licensed Software only if so -specifically agreed between the Parties. - -"Agreement Term" shall mean the validity period of this Agreement, as set forth -in Section 12. - -"Applications" shall mean software products created using the Licensed -Software, which include the Redistributables, or part thereof. - -"Contractor(s)" shall mean third party consultants, distributors and -contractors performing services to the Licensee under applicable contractual -arrangement. - -"Customer(s)" shall mean Licensee's customers to whom Licensee, directly or -indirectly, distributes copies of the Redistributables as integrated or -incorporated into Applications or Devices. - -"Data Protection Legislation" shall mean the General Data Protection Regulation -(EU 2016/679) (GDPR) and any national implementing laws, regulations and -secondary legislation, as may be amended or updated from time to time, as well -as any other data protection laws or regulations applicable in relevant -territory. - -"Deployment Platforms" shall mean target operating systems and/or hardware -specified in the License Certificate, on which the Redistributables can be -distributed pursuant to the terms and conditions of this Agreement. - -"Designated User(s)" shall mean the employee(s) of Licensee or Licensee's -Affiliates acting within the scope of their employment or Licensee's -Contractors acting within the scope of their services on behalf of Licensee. - -"Development License" shall mean the license needed by the Licensee for each -Designated User to use the Licensed Software under the license grant described -in Section 3.1 of this Agreement. Development Licenses are available per -respective Licensed Software products, each product having its designated scope -and purpose of use. - -"Development License Term" shall mean the agreed validity period of the -Development License or QA Tools license during which time the relevant Licensed -Software product can be used pursuant to this Agreement. Agreed Development -License Term, as ordered and paid for by the Licensee, shall be memorialized in -the applicable License Certificate. - -"Development Platforms" shall mean those host operating systems specified in -the License Certificate, in which the Licensed Software can be used under the -Development License. - -"Devices" shall mean - (1) hardware devices or products that - i. are manufactured and/or distributed by the Licensee, its Affiliates, - Contractors or Customers, and - ii. incorporate, integrate or link to Applications such that - substantial functionality of such unit, when used by an End User, - is provided by Application(s) or otherwise depends on the Licensed - Software, regardless of whether the Application is developed by - Licensee or its Contractors; or - (2) Applications designed for the hardware devices specified in item (1). - - Devices covered by this Agreement shall be specified in Appendix 2 or in a - quote. - -"Distribution License(s)" shall mean a royalty-bearing license required for any -kind of sale, trade, exchange, loan, lease, rental or other distribution by or -on behalf of Licensee to a third party of Redistributables in connection with -Devices pursuant to license grant described in Section 3.3 of this Agreement. -Distribution Licensed are sold separately for each type of Device respectively -and cannot be used for any type of Devices at Licensee's discretion. - -"Distribution License Packs" shall mean set of prepaid Distribution Licenses -for distribution of Redistributables, as defined in The Qt Company's standard -price list, quote, Purchase Order confirmation or in an Appendix 2 hereto, as -the case may be. - -"End User" shall mean the final end user of the Application or a Device. - -"Evaluation License Term" shall mean a time period specified in the License -Certificate for the Licensee to use the relevant Licensed Software for -evaluation purposes according to Section 3.6 herein. - -"Intellectual Property Rights" shall mean patents (including utility models), -design patents, and designs (whether or not capable of registration), chip -topography rights and other like protection, copyrights, trademarks, service -marks, trade names, logos or other words or symbols and any other form of -statutory protection of any kind and applications for any of the foregoing as -well as any trade secrets. - -"License Certificate" shall mean a certificate generated by The Qt Company for -each Designated User respectively upon them downloading the Licensed Software, -which will be available under respective Designated User's Qt Account at -account.qt.io. License Certificates will specify relevant information -pertaining the Licensed Software purchased by Licensee and Designated User's -license to the Licensed Software. - -"License Fee" shall mean the fee charged to the Licensee for rights granted -under the terms of this Agreement. - -"Licensed Software" shall mean specified product of commercially licensed -version of Qt Software and/or QA Tools defined in Appendix 1 and/or Appendix 3, -which Licensee has purchased and which is provided to Licensee under the terms -of this Agreement. Licensed Software shall include corresponding online or -electronic documentation, associated media and printed materials, including the -source code (where applicable), example programs and the documentation. -Licensed Software does not include Third Party Software (as defined in Section -4) or Open Source Qt. The Qt Company may, in the course of its development -activities, at its free and absolute discretion and without any obligation to -send or publish any notifications to the Licensee or in general, make changes, -additions or deletions in the components and functionalities of the Licensed -Software, provided that no such changes, additions or deletions will affect -the already released version of the Licensed Software, but only upcoming -version(s). - -"Licensee" shall mean the individual or legal entity that is party to this -Agreement. - -"Licensee's Records" shall mean books and records that contain information -bearing on Licensee's compliance with this Agreement, Licensee's use of Open -Source Qt and/or the payments due to The Qt Company under this Agreement, -including, but not limited to user information, assembly logs, sales records -and distribution records. - -"Modified Software" shall have the meaning as set forth in Section 2.3. - -"Online Services" shall mean any services or access to systems made available -by The Qt Company to the Licensee over the Internet relating to the Licensed -Software or for the purpose of use by the Licensee of the Licensed Software or -Support. Use of any such Online Services is discretionary for the Licensee and -some of them may be subject to additional fees. - -"Open Source Qt" shall mean Qt Software available under the terms of the GNU -Lesser General Public License, version 2.1 or later ("LGPL") or the GNU General -Public License, version 2.0 or later ("GPL"). For clarity, Open Source Qt shall -not be provided, governed or used under this Agreement. - -"Party" or "Parties" shall mean Licensee and/or The Qt Company. - -"Permitted Software" shall mean (i) third party open source software products -that are generally available for public in source code form and free of any -charge under any of the licenses approved by Open Source Initiative as listed -on https://opensource.org/licenses, which may include parts of Open Source Qt -or be developed using Open Source Qt; and (ii) software The Qt Company has made -available via its Qt Marketplace online distribution channel. - -"Pre-Release Code" shall have the meaning as set forth in Section 4. - -"Prohibited Combination" shall mean any effort to use, combine, incorporate, -link or integrate Licensed Software with any software created with or -incorporating Open Source Qt, or use Licensed Software for creation of any such -software. - -"Purchase Order" shall have the meaning as set forth in Section 10.2. - -"QA Tools" shall mean software libraries and tools as defined in Appendix 1 -depending on which product(s) the Licensee has purchased under the Agreement. - -"Qt Software" shall mean the software libraries and tools of The Qt Company, -which The Qt Company makes available under commercial and/or open source -licenses. - -"Redistributables" shall mean the portions of the Licensed Software set forth -in Appendix 1 that may be distributed pursuant to the terms of this Agreement -in object code form only, including any relevant documentation. Where relevant, -any reference to Licensed Software in this Agreement shall include and refer -also to Redistributables. - -"Renewal Term" shall mean an extension of previous Development License Term as -agreed between the Parties. - -"Submitted Modified Software" shall have the meaning as set forth in Section -2.3. - -"Support" shall mean standard developer support that is provided by The Qt -Company to assist Designated Users in using the Licensed Software in accordance -with this Agreement and the Support Terms. - -"Support Terms" shall mean The Qt Company's standard support terms specified in -Appendix 9 hereto. - -"Taxes" shall have the meaning set forth in Section 10.5. - -"The Qt Company" shall mean: - (i) in the event Licensee is an individual residing in the United States or - a legal entity incorporated in the United States or having its - headquarters in the United States, The Qt Company Inc., a Delaware - corporation with its office at 3031 Tisch Way, 110 Plaza West, - San Jose, CA 95128, USA.; or - (ii) in the event the Licensee is an individual residing outside of the - United States or a legal entity incorporated outside of the United - States or having its registered office outside of the United States, - The Qt Company Ltd., a Finnish company with its registered office at - Miestentie 7, 02150 Espoo, Finland. - -"Third-Party Software" shall have the meaning set forth in Section 4. - -"Updates" shall mean a release or version of the Licensed Software containing -bug fixes, error corrections and other changes that are generally made -available to users of the Licensed Software that have contracted for Support. -Updates are generally depicted as a change to the digits following the decimal -in the Licensed Software version number. The Qt Company shall make Updates -available to the Licensee under the Support. Updates shall be considered as -part of the Licensed Software hereunder. - -"Upgrades" shall mean a release or version of the Licensed Software containing -enhancements and new features and are generally depicted as a change to the -first digit of the Licensed Software version number. In the event Upgrades are -provided to the Licensee under this Agreement, they shall be considered as part -of the Licensed Software hereunder. - -2. OWNERSHIP - -2.1. Ownership of The Qt Company - -The Licensed Software is protected by copyright laws and international -copyright treaties, as well as other intellectual property laws and treaties. -The Licensed Software is licensed, not sold. - -All of The Qt Company's Intellectual Property Rights are and shall remain the -exclusive property of The Qt Company or its licensors respectively. No rights -to The Qt Company's Intellectual Property Rights are assigned or granted to -Licensee under this Agreement, except when and to the extent expressly -specified herein. - -2.2. Ownership of Licensee - -All the Licensee's Intellectual Property Rights are and shall remain the -exclusive property of the Licensee or its licensors respectively. - -All Intellectual Property Rights to the Modified Software, Applications and -Devices shall remain with the Licensee and no rights thereto shall be granted -by the Licensee to The Qt Company under this Agreement (except as set forth in -Section 2.3 below). - -2.3. Modified Software - -Licensee may create bug-fixes, error corrections, patches or modifications to -the Licensed Software ("Modified Software"). Such Modified Software may break -the source or binary compatibility with the Licensed Software (including -without limitation through changing the application programming interfaces -("API") or by adding, changing or deleting any variable, method, or class -signature in the Licensed Software and/or any inter-process protocols, -services or standards in the Licensed Software libraries). To the extent that -Licensee's Modified Software so breaks source or binary compatibility with the -Licensed Software, Licensee acknowledges that The Qt Company's ability to -provide Support may be prevented or limited and Licensee's ability to make use -of Updates may be restricted. - -Licensee may, at its sole and absolute discretion, choose to submit Modified -Software to The Qt Company ("Submitted Modified Software") in connection with -Licensee's Support request, service request or otherwise. In the event -Licensee does so, then, Licensee hereby grants The Qt Company a sublicensable, -assignable, irrevocable, perpetual, worldwide, non-exclusive, royalty-free and -fully paid-up license, under all of Licensee's Intellectual Property Rights, to -reproduce, adapt, translate, modify, and prepare derivative works of, publicly -display, publicly perform, sublicense, make available and distribute such -Submitted Modified Software as The Qt Company sees fit at its free and absolute -discretion. - -3. LICENSES GRANTED - -3.1. Development with Licensed Software - -Subject to the terms of this Agreement, The Qt Company grants to Licensee a -worldwide, non-exclusive, non-transferable license, valid for each Development -License Term, to use, modify and copy the Licensed Software by Designated -Users on the Development Platforms for the sole purposes of designing, -developing, demonstrating and testing Application(s) and/or Devices, and to -provide thereto related support and other related services to Customers. Each -Application and/or Device can only include, incorporate or integrate -contributions by such Designated Users who are duly licensed for the applicable -Development Platform(s) and Deployment Platform(s) (i.e have a valid license -for the appropriate Licensed Software product). - -Licensee may install copies of the Licensed Software on five (5) computers per -Designated User, provided that only the Designated Users who have a valid -Development License may use the Licensed Software. - -Licensee may at any time designate another Designated User to replace a -then-current Designated User by notifying The Qt Company in writing, where such -replacement is due to termination of employment, change of job duties, long -time absence or other such permanent reason affecting Designated User's need -for Licensed Software. - -Upon expiry of the initially agreed Development License Term, the respective -Development License Term shall be automatically extended to one or more Renewal -Term(s), unless and until either Party notifies the other Party in writing, or -any other method acceptable to The Qt Company (it being specifically -acknowledged and understood that verbal notification is explicitly deemed -inadequate in all circumstances), that it does not wish to continue the -Development License Term, such notification to be provided to the other Party -no less than thirty (30) days before expiry of the respective Development -License Term. The Qt Company shall, in good time before the due date for the -above notification, remind the Licensee on the coming Renewal Term. Unless -otherwise agreed between the Parties, Renewal Term shall be 12 months. - -Any such Renewal Term shall be subject to License Fees agreed between the -Parties or, if no advance agreement exists, subject to The Qt Company's -standard list pricing applicable at the commencement date of any such -Renewal Term. - -The Qt Company may either request the Licensee to place a purchase order -corresponding to a quote by The Qt Company, or use Licensee's stored Credit -Card information in the Qt Account to automatically charge the Licensee for the -relevant Renewal Term. - -3.2. Distribution of Applications - -Subject to the terms of this Agreement, The Qt Company grants to Licensee a -worldwide, non-exclusive, non-transferable, revocable (for cause pursuant to -this Agreement), right and license, valid for the Agreement Term, to - (i) distribute, by itself or through its Contractors, Redistributables as - installed, incorporated or integrated into Applications for execution - on the Deployment Platforms, and - (ii) grant perpetual and irrevocable sublicenses to Redistributables, as - distributed hereunder, for Customers solely to the extent necessary in - order for the Customers to use the Applications for their respective - intended purposes. - -Right to distribute the Redistributables as part of an Application as provided -herein is not royalty-bearing but is conditional upon the Application having -been created, updated and maintained under a valid and duly paid Development -Licenses. - -3.3. Distribution of Devices - -Subject to the terms of this Agreement, The Qt Company grants to Licensee a -worldwide, non-exclusive, non-transferable, revocable (for cause pursuant to -this Agreement), right and license, valid for the Agreement Term, to - (i) distribute, by itself or through one or more tiers of Contractors, - Redistributables as installed, incorporated or integrated, or intended - to be installed, incorporated or integrated into Devices for execution - on the Deployment Platforms, and - (ii) grant perpetual and irrevocable sublicenses to Redistributables, as - distributed hereunder, for Customers solely to the extent necessary in - order for the Customers to use the Devices for their respective - intended purposes. - -Right to distribute the Devices as provided herein is conditional upon - (i) the Devices having been created, updated and maintained under a valid - and duly paid Development Licenses, and - (ii) the Licensee having acquired corresponding Distribution Licenses at - the time of distribution of any Devices to Customers. - -3.4. Further Requirements - -The licenses granted above in this Section 3 by The Qt Company to Licensee are -conditional and subject to Licensee's compliance with the following terms: - (i) Licensee acknowledges that The Qt Company has separate products of - Licensed Software for the purpose of Applications and Devices - respectively, where development and distribution of Devices is only - allowed using the correct designated product. Licensee shall make sure - and bear the burden of proof that Licensee is using a correct product - of Licensed Software entitling Licensee to development and distribution - of Devices; - (ii) Licensee shall not remove or alter any copyright, trademark or other - proprietary rights notice(s) contained in any portion of the Licensed - Software; - (iii) Applications must add primary and substantial functionality to the - Licensed Software so as not to compete with the Licensed Software; - (iv) Applications may not pass on functionality which in any way makes it - possible for others to create software with the Licensed Software; - provided however that Licensee may use the Licensed Software's - scripting and QML ("Qt Quick") functionality solely in order to enable - scripting, themes and styles that augment the functionality and - appearance of the Application(s) without adding primary and substantial - functionality to the Application(s); - (v) Licensee shall not use Licensed Software in any manner or for any - purpose that infringes, misappropriates or otherwise violates any - Intellectual property or right of any third party, or that violates any - applicable law; - (vi) Licensee shall not use The Qt Company's or any of its suppliers' - names, logos, or trademarks to market Applications, except that - Licensee may use "Built with Qt" logo to indicate that Application(s) - or Device(s) was developed using the Licensed Software; - (vii) Licensee shall not distribute, sublicense or disclose source code of - Licensed Software to any third party (provided however that Licensee - may appoint employee(s) of Contractors and Affiliates as Designated - Users to use Licensed Software pursuant to this Agreement). Such right - may be available for the Licensee subject to a separate software - development kit ("SDK") license agreement to be concluded with The Qt - Company; - (viii) Licensee shall not grant the Customers a right to (a) make copies of - the Redistributables except when and to the extent required to use the - Applications and/or Devices for their intended purpose, (b) modify the - Redistributables or create derivative works thereof, (c) decompile, - disassemble or otherwise reverse engineer Redistributables, or (d) - redistribute any copy or portion of the Redistributables to any third - party, except as part of the onward sale of the Application or Device - on which the Redistributables are installed; - (ix) Licensee shall not and shall cause that its Affiliates or Contractors - shall not use Licensed Software in any Prohibited Combination, unless - Licensee has received an advance written permission from The Qt Company - to do so. Absent such written permission, any and all distribution by - the Licensee during the Agreement Term of a hardware device or product - a) which incorporate or integrate any part of Licensed Software or Open - Source Qt; or b) where substantial functionality is provided by - software built with Licensed Software or Open Source Qt or otherwise - depends on the Licensed Software or Open Source Qt, shall be considered - to be Device distribution under this Agreement and shall be dependent - on Licensee's compliance thereof (including but not limited to - obligation to pay applicable License Fees for such distribution). - Notwithstanding what is provided above in this sub-section (ix), - Licensee is entitled to use and combine Licensed Software with any - Permitted Software; - (x) Licensee shall cause all of its Affiliates, Contractors and Customers - entitled to make use of the licenses granted under this Agreement, to - be contractually bound to comply with the relevant terms of this - Agreement and not to use the Licensed Software beyond the terms hereof - and for any purposes other than operating within the scope of their - services for Licensee. Licensee shall be responsible for any and all - actions and omissions of its Affiliates and Contractors relating to the - Licensed Software and use thereof (including but not limited to payment - of all applicable License Fees); - (xi) Except when and to the extent explicitly provided in this Section 3, - Licensee shall not transfer, publish, disclose, display or otherwise - make available the Licensed Software; and - (xii) Licensee shall not attempt or enlist a third party to conduct or - attempt to conduct any of the above. - -Above terms shall not be applicable if and to the extent they conflict with any -mandatory provisions of any applicable laws. - -Any use of Licensed Software beyond the provisions of this Agreement is -strictly prohibited and requires an additional license from The Qt Company. - -3.5 QA Tools License - -Subject to the terms of this Agreement, The Qt Company grants to Licensee a -worldwide, non-exclusive, non-transferable license, valid for the Development -License Term, to use the QA Tools for Licensee's internal business purposes in -the manner provided below and in Appendix 1 hereto. - -Licensee may modify the QA Tools except for altering or removing any details of -ownership, copyright, trademark or other property right connected with the QA -Tools. - -Licensee shall not distribute the QA Tools or any part thereof, modified or -unmodified, separately or as part of any software package, Application or -Device. - -Upon expiry of the initially agreed Development License Term, the respective -Development License Term shall be automatically extended to one or more Renewal -Term(s), unless and until either Party notifies the other Party in writing, or -any other method acceptable to The Qt Company (it being specifically -acknowledged and understood that verbal notification is explicitly deemed -inadequate in all circumstances), that it does not wish to continue the -Development License Term, such notification to be provided to the other Party -no less than thirty (30) days before expiry of the respective Development -License Term. The Qt Company shall, in good time before the due date for the -above notification, remind the Licensee on the coming Renewal Term. Unless -otherwise agreed between the Parties, Renewal Term shall be 12 months. - -Any such Renewal Term shall be subject to License Fees agreed between the -Parties or, if no advance agreement exists, subject to The Qt Company's -standard list pricing applicable at the commencement date of any such -Renewal Term. - -3.6 Evaluation License - -Subject to the terms of this Agreement, The Qt Company grants to Licensee a -worldwide, non-exclusive, non-transferable license, valid for the Evaluation -License Term to use the Licensed Software solely for the Licensee's internal -use to evaluate and determine whether the Licensed Software meets Licensee's -business requirements, specifically excluding any commercial use of the -Licensed Software or any derived work thereof. - -Upon the expiry of the Evaluation License Term, Licensee must either -discontinue use of the relevant Licensed Software or acquire a commercial -Development License or QA Tools License specified herein. - -4. THIRD-PARTY SOFTWARE - -The Licensed Software may provide links or access to third party libraries or -code (collectively "Third-Party Software") to implement various functions. -Third-Party Software does not, however, comprise part of the Licensed Software, -but is provided to Licensee complimentary and use thereof is discretionary for -the Licensee. Third-Party Software will be listed in the ".../src/3rdparty" -source tree delivered with the Licensed Software or documented in the Licensed -Software, as such may be amended from time to time. Licensee acknowledges that -use or distribution of Third-Party Software is in all respects subject to -applicable license terms of applicable third-party right holders. - -5. PRE-RELEASE CODE - -The Licensed Software may contain pre-release code and functionality, or sample -code marked or otherwise stated with appropriate designation such as -"Technology Preview", "Alpha", "Beta", "Sample", "Example" etc. -("Pre-Release Code"). - -Such Pre-Release Code may be present complimentary for the Licensee, in order -to provide experimental support or information for new platforms or -preliminary versions of one or more new functionalities or for other similar -reasons. The Pre-Release Code may not be at the level of performance and -compatibility of a final, generally available, product offering. The -Pre-Release Code may not operate correctly, may contain errors and may be -substantially modified by The Qt Company prior to the first commercial -product release, if any. The Qt Company is under no obligation to make -Pre-Release Code commercially available, or provide any Support or Updates -relating thereto. The Qt Company assumes no liability whatsoever regarding -any Pre-Release Code, but any use thereof is exclusively at Licensee's own risk -and expense. - -For clarity, unless Licensed Software specifies different license terms for the -respective Pre-Release Code, the Licensee is entitled to use such pre-release -code pursuant to Section 3, just like other Licensed Software. - -6. LIMITED WARRANTY AND WARRANTY DISCLAIMER - -The Qt Company hereby represents and warrants that (i) it has the power and -authority to grant the rights and licenses granted to Licensee under this -Agreement, and (ii) Licensed Software will operate materially in accordance -with its specifications. - -Except as set forth above, the Licensed Software is licensed to Licensee "as -is" and Licensee's exclusive remedy and The Qt Company's entire liability for -errors in the Licensed Software shall be limited, at The Qt Company's option, -to correction of the error, replacement of the Licensed Software or return of -the applicable fees paid for the defective Licensed Software for the time -period during which the License is not able to utilize the Licensed Software -under the terms of this Agreement. - -TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW, THE QT COMPANY ON BEHALF OF -ITSELF AND ITS LICENSORS, SUPPLIERS AND AFFILIATES, DISCLAIMS ALL OTHER -WARRANTIES, EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, ANY IMPLIED -WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND -NON-INFRINGEMENT WITH REGARD TO THE LICENSED SOFTWARE. THE QT COMPANY DOES NOT -WARRANT THAT THE LICENSED SOFTWARE WILL SATISFY LICENSEE'S REQUIREMENTS OR THAT -IT WILL OPERATE WITHOUT DEFECT OR ERROR OR THAT THE OPERATION THEREOF WILL BE -UNINTERRUPTED. - -7. LIMITATION OF LIABILITY - -EXCEPT FOR (I) CASES OF GROSS NEGLIGENCE OR INTENTIONAL MISCONDUCT, AND (II) -BREACH OF CONFIDENTIALITY, AND TO THE EXTENT PERMITTED BY APPLICABLE LAW, IN NO -EVENT SHALL EITHER PARTY BE LIABLE TO THE OTHER PARTY FOR ANY LOSS OF PROFIT, -LOSS OF DATA, LOSS OF BUSINESS OR GOODWILL OR ANY OTHER INDIRECT, SPECIAL, -CONSEQUENTIAL, INCIDENTAL OR PUNITIVE COST, DAMAGES OR EXPENSE OF ANY KIND, -HOWSOEVER ARISING UNDER OR IN CONNECTION WITH THIS AGREEMENT. - -EXCEPT FOR (I) CASES OF GROSS NEGLIGENCE OR INTENTIONAL MISCONDUCT, AND (II) -BREACH OF CONFIDENTIALITY, AND TO THE EXTENT PERMITTED BY APPLICABLE LAW, IN NO -EVENT SHALL EITHER PARTY'S TOTAL AGGREGATE LIABILITY UNDER THIS AGREEMENT -EXCEED THE AGGREGATE LICENSE FEES PAID OR PAYABLE TO THE QT COMPANY BY LICENSEE -DURING THE DEVELOPMENT LICENSE TERM DURING WHICH THE EVENT RESULTING IN SUCH -LIABILITY OCCURRED. - -THE PROVISIONS OF THIS SECTION 7 ALLOCATE THE RISKS UNDER THIS AGREEMENT -BETWEEN THE QT COMPANY AND LICENSEE AND THE PARTIES HAVE RELIED UPON THE -LIMITATIONS SET FORTH HEREIN IN DETERMINING WHETHER TO ENTER INTO THIS -AGREEMENT. - -NOTWITHSTANDING ANYTHING TO THE CONTRARY IN THIS AGREEMENT, LICENSEE SHALL -ALWAYS BE LIABLE TO PAY THE APPLICABLE LICENSE FEES CORRESPONDING TO ITS -ACTUAL USE OF LICENSED SOFTWARE. - -8. SUPPORT, UPDATES AND ONLINE SERVICES - -Upon due payment of the agreed License Fees the Licensee will be eligible to -receive Support and Updates and to use the Online Services during the agreed -Development License Term or other agreed fixed time period. Support is -provided according to agreed support level and subject to applicable -requirements and restrictions, as specified in the Support Terms. - -Unless otherwise decided by The Qt Company at its free and absolute discretion, -Upgrades will not be included in the Support but may be available subject to -additional fees. - -From time to time The Qt Company may change the Support Terms, provided that -during the respective ongoing Support period the level of Support may not be -reduced without the consent of the Licensee. - -Unless otherwise agreed, The Qt Company shall not be responsible for providing -any service or support to Customers. - -9. CONFIDENTIALITY - -Each Party acknowledges that during the Agreement Term each Party may receive -information about the other Party's business, business methods, business plans, -customers, business relations, technology, and other information, including the -terms of this Agreement, that is confidential and of great value to the other -Party, and the value of which would be significantly reduced if disclosed to -third parties ("Confidential Information"). Accordingly, when a Party (the -"Receiving Party") receives Confidential Information from the other Party (the -"Disclosing Party"), the Receiving Party shall only disclose such information -to employees and Contractors on a need to know basis, and shall cause its -employees and employees of its Affiliates to: (i) maintain any and all -Confidential Information in confidence; (ii) not disclose the Confidential -Information to a third party without the Disclosing Party's prior written -approval; and (iii) not, directly or indirectly, use the Confidential -Information for any purpose other than for exercising its rights and -fulfilling its responsibilities pursuant to this Agreement. Each Party shall -take reasonable measures to protect the Confidential Information of the other -Party, which measures shall not be less than the measures taken by such Party -to protect its own confidential and proprietary information. - -Obligation of confidentiality shall not apply to information that (i) is or -becomes generally known to the public through no act or omission of the -Receiving Party; (ii) was in the Receiving Party's lawful possession prior to -the disclosure hereunder and was not subject to limitations on disclosure or -use; (iii) is developed independently by employees or Contractors of the -Receiving Party or other persons working for the Receiving Party who have not -had access to the Confidential Information of the Disclosing Party, as proven -by the written records of the Receiving Party; (iv) is lawfully disclosed to -the Receiving Party without restrictions, by a third party not under an -obligation of confidentiality; or (v) the Receiving Party is legally compelled -to disclose, in which case the Receiving Party shall notify the Disclosing -Party of such compelled disclosure and assert the privileged and confidential -nature of the information and cooperate fully with the Disclosing Party to -limit the scope of disclosure and the dissemination of disclosed Confidential -Information to the minimum extent necessary. - -The obligations under this Section 9 shall continue to remain in force for a -period of five (5) years after the last disclosure, and, with respect to trade -secrets, for so long as such trade secrets are protected under applicable trade -secret laws. - -10. FEES, DELIVERY AND PAYMENT - -10.1. License Fees - -License Fees are described in The Qt Company's standard price list, quote or -Purchase Order confirmation or in an Appendix 2 hereto, as the case may be. - -Unless otherwise expressly provided in this Agreement, the License Fees shall -not be refunded or claimed as a credit in any event or for any reason -whatsoever. - -10.2. Ordering Licenses - -Licensee may purchase Development Licenses, Distribution Licenses and QA Tools -Licenses pursuant to agreed pricing terms or, if no specific pricing terms have -been agreed upon, at The Qt Company's standard pricing terms applicable at the -time of purchase. - -Unless expressly otherwise agreed, any price or other term quoted to the -Licensee or specified herein shall only be valid for the thirty (30) days from -the effective date of this Agreement, Appendix 2 or the date of the quote, as -applicable. - -Licensee shall submit all purchase orders for Development Licenses and -Distribution Licenses to The Qt Company by email or any other method acceptable -to The Qt Company (each such order is referred to herein as a "Purchase Order") -for confirmation, whereupon the Purchase Order shall become binding between the -Parties. - -Licensee acknowledges and agrees that all Purchase Orders for Licensed Software -the Licensee makes during the Agreement Term shall be governed exclusively -under the terms of this Agreement. - -10.3. Distribution License Packs - -Unless otherwise agreed, Distribution Licenses shall be purchased by way of -Distribution License Packs. - -Upon due payment of the ordered Distribution License Pack(s), the Licensee will -have an account of Distribution Licenses available for distributing the -Redistributables in accordance with this Agreement. - -Each time Licensee distributes a copy of Redistributables, then one -Distribution License is used, and Licensee's account of available Distribution -Licenses is decreased accordingly. - -Licensee may distribute copies of the Redistributables so long as Licensee has -Distribution Licenses remaining on its account. - -10.4. Payment Terms - -License Fees and any other charges under this Agreement shall be paid by -Licensee no later than thirty (30) days from the date of the applicable invoice -from The Qt Company. - -The Qt Company will submit an invoice to Licensee after the date of this -Agreement and/or after The Qt Company receives a Purchase Order from Licensee. - -A late payment charge of the lower of (a) one percent per month; or (b) the -interest rate stipulated by applicable law, shall be charged on any unpaid -balances that remain past due and which have not been disputed by the Licensee -in good faith. - -10.5. Taxes - -All License Fees and other charges payable hereunder are gross amounts but -exclusive of any value added tax, use tax, sales tax, withholding tax and other -taxes, duties or tariffs ("Taxes") levied directly for the sale, delivery or -use of Licensed Software hereunder pursuant to any applicable law. Such -applicable Taxes shall be paid by Licensee to The Qt Company, or, where -applicable, in lieu of payment of such Taxes to The Qt Company, Licensee shall -provide an exemption certificate to The Qt Company and any applicable -authority. - -11. RECORD-KEEPING AND REPORTING OBLIGATIONS; AUDIT RIGHTS - -11.1. Licensee's Record-keeping - -Licensee shall at all times during the Agreement Term and for a period of two -(2) years thereafter maintain Licensee's Records in an accurate and up-to-date -form. Licensee's Records shall be adequate to reasonably enable The Qt Company -to determine Licensee's compliance with the provisions of this Agreement. The -records shall conform to general good accounting practices. - -Licensee shall, within thirty (30) days from receiving The Qt Company's request -to that effect, deliver to The Qt Company a report based on Licensee's Records, -such report to contain information, in sufficient detail, on (i) number and -identity of users working with Licensed Software or Open Source Qt, (ii) copies -of Redistributables distributed by Licensee during the most recent calendar -quarter and/or any other term specified by The Qt Company, , and (iii) any -other information pertaining to Licensee's compliance with the terms of this -Agreement (like e.g. information on products and/or projects relating to use of -Distribution Licenses), as The Qt Company may reasonably require from time to -time. - -11.2. The Qt Company's Audit Rights - -The Qt Company or an independent auditor acting on behalf of The Qt Company's, -may, upon at least thirty (30) days' prior written notice and at its expense, -audit Licensee with respect to the Licensee's use of the Licensed Software, but -not more frequently than once during each 6-month period. Such audit may be -conducted by mail, electronic means or through an in-person visit to Licensee's -place of business. Any possible in-person audit shall be conducted during -regular business hours at Licensee's facilities and shall not unreasonably -interfere with Licensee's business activities and shall be limited in scope to -verify Licensee's compliance with the terms of this Agreement. The Qt Company -or the independent auditor acting on behalf of The Qt Company shall be entitled -to inspect Licensee's Records and conduct necessary interviews of Licensee's -relevant employees and Contractors. All such Licensee's Records and use thereof -shall be subject to an obligation of confidentiality under this Agreement. - -If an audit reveals that Licensee is using the Licensed Software beyond scope -of the licenses Licensee has paid for, Licensee shall pay to The Qt Company any -amounts owed for such unauthorized use within 30 days from receipt of the -corresponding invoice from The Qt Company. - -In addition, in the event the audit reveals a material violation of the terms -of this Agreement (without limitation, either (i) underpayment of more than 10 -% of License Fees or 10,000 euros (whichever is more) or (ii) distribution of -products, which include or result from Prohibited Combination, shall be deemed -a material violation for purposes of this section), then the Licensee shall -pay The Qt Company's reasonable cost of conducting such audit. - -12. TERM AND TERMINATION - -12.1. Agreement Term - -This Agreement shall enter into force upon due acceptance by both Parties and -remain in force until terminated pursuant to the terms of this Section 12 -("Agreement Term"). - -12.2. Termination for breach and suspension of rights -Either Party shall have the right to terminate this Agreement upon thirty (30) -days prior written notice if the other Party commits a material breach of any -obligation of this Agreement and fails to remedy such breach within such notice -period. - -Instead of termination, The Qt Company shall have the right to suspend or -withhold grants of all rights to the Licensed Software hereunder, including but -not limited to the Development Licenses, Distribution License, and Support, -should Licensee fail to make payment in timely fashion or otherwise violates or -is reasonably suspected to violate its obligations or terms of this Agreement, -and where such violation or breach is not cured within ten (10) business days -following The Qt Company's written notice thereof. - -12.3. Termination for insolvency - -Either Party shall have the right to terminate this Agreement immediately upon -written notice in the event that the other Party becomes insolvent, files for -any form of bankruptcy, makes any assignment for the benefit of creditors, has -a receiver, administrative receiver or officer appointed over the whole or a -substantial part of its assets, ceases to conduct business, or an act -equivalent to any of the above occurs under the laws of the jurisdiction of the -other Party. - -12.4. Parties' Rights and Duties upon Termination - -Upon expiry or termination of the Agreement, Licensee shall cease and shall -cause all Designated Users (including those of its Affiliates' and -Contractors') to cease using the Licensed Software under this Agreement. For -clarity, a Development License of a Designated User or a QA Tools License, and -all rights relating thereto, shall always terminate at the expiry of the -respective Development License Term, even if the Agreement continues to remain -in force. - -Upon such termination the Licensee shall destroy or return to The Qt Company -all copies of the Licensed Software and all related materials and will certify -the same by Licensee's duly authorized officer to The Qt Company upon its -request, provided however that Licensee may retain and exploit such copies of -the Licensed Software as it may reasonably require in providing continued -support to Customers. - -Except when this Agreement is terminated by The Qt Company due to Licensee's -material breach as set forth in Section 12.2, the Licensee may continue -distribution of Applications and Devices under the terms of this Agreement -despite the termination of this Agreement. In such event the terms hereof will -continue to be applicable and govern any such distribution of Applications and -Devices beyond the expiry or termination of this Agreement. In case of -termination by The Qt Company due to Licensee's material breach, Licensee must -cease any distribution of Applications and Devices at the date of termination -of this Agreement. - -Expiry or termination of this Agreement for any reason whatsoever shall not -relieve Licensee of its obligation to pay any License Fees accrued or payable -to The Qt Company prior to the effective date of termination, and Licensee pay -to The Qt Company all such fees within 30 days from the effective date of -termination of this Agreement. - -Termination of this Agreement shall not affect any rights of Customers to -continue use of Applications and Devices (and therein incorporated -Redistributables). - -12.5. Extension of Rights under Special Circumstances - -In the event of The Qt Company choosing not to renew the Development License(s) -or QA Tools Licenses, as set forth in Section 3.1 and 3.5 respectively, and -where such decision of non-renewal is not due to any ongoing breach or alleged -breach (as reasonably determined by The Qt Company) by Licensee of the terms of -this Agreement or any applicable license terms of Open Source Qt, then all -valid and affected Development Licenses and QA Tools licenses possessed by the -Licensee at such date shall be extended to be valid in perpetuity under the -terms of this Agreement and Licensee is entitled to purchase additional -licenses as set forth in Section 10.2. - -In the event The Qt Company is declared bankrupt under a final, non-cancellable -decision by relevant court of law, and this Agreement is not, at the date of -expiry of the Development License(s) or QA Tools Licenses, assigned to party, -who has assumed The Qt Company's position as a legitimate licensor of Licensed -Software under this Agreement, then all valid Development Licenses and QA Tools -Licenses possessed by the Licensee at such date of expiry, and which the -Licensee has not notified for expiry, shall be extended to be valid in -perpetuity under the terms of this Agreement. - -For clarity, in case of an extension under this Section 12.5, any such -extension shall not apply to The Qt Company's Support obligations, but Support -shall be provided only up until the end of the respective fixed Development -License Term regardless of the extension of relevant Development License or QA -Tools License, unless otherwise agreed between the Parties. - -13. GOVERNING LAW AND LEGAL VENUE - -In the event this Agreement is in the name of The Qt Company Inc., a Delaware -Corporation, then: - (i) this Agreement shall be construed and interpreted in accordance with - the laws of the State of California, USA, excluding its choice of law - provisions; - (ii) the United Nations Convention on Contracts for the International Sale - of Goods will not apply to this Agreement; and - (iii) any dispute, claim or controversy arising out of or relating to this - Agreement or the breach, termination, enforcement, interpretation or - validity thereof, including the determination of the scope or - applicability of this Agreement to arbitrate, shall be determined by - arbitration in San Francisco, USA, before one arbitrator. The - arbitration shall be administered by JAMS pursuant to JAMS' Streamlined - Arbitration Rules and Procedures. Judgment on the Award may be entered - in any court having jurisdiction. This Section shall not preclude - parties from seeking provisional remedies in aid of arbitration from a - court of appropriate jurisdiction. - -In the event this Agreement is in the name of The Qt Company Ltd., a Finnish -Company, then: - (i) this Agreement shall be construed and interpreted in accordance with - the laws of Finland, excluding its choice of law provisions; - (ii) the United Nations Convention on Contracts for the International Sale - of Goods will not apply to this Agreement; and - (iii) any disputes, controversy or claim arising out of or relating to this - Agreement, or the breach, termination or validity thereof shall be - finally settled by arbitration in accordance with the Arbitration Rules - of International Chamber of Commerce. The arbitration tribunal shall - consist of one (1), or if either Party so requires, of three (3), - arbitrators. The award shall be final and binding and enforceable in - any court of competent jurisdiction. The arbitration shall be held in - Helsinki, Finland and the process shall be conducted in the English - language. This Section shall not preclude parties from seeking - provisional remedies in aid of arbitration from a court of appropriate - jurisdiction. - -14. GENERAL PROVISIONS - -14.1. No Assignment - -Except in the case of a merger or sale of substantially all of its corporate -assets, Licensee shall not be entitled to assign or transfer all or any of its -rights, benefits and obligations under this Agreement without the prior written -consent of The Qt Company, which shall not be unreasonably withheld or delayed. -The Qt Company shall be entitled to freely assign or transfer any of its -rights, benefits or obligations under this Agreement. - -14.2. No Third-Party Representations - -Licensee shall make no representations or warranties concerning the Licensed -Software on behalf of The Qt Company. Any representation or warranty Licensee -makes or purports to make on The Qt Company's behalf shall be void as to -The Qt Company. - -14.3. Surviving Sections - -Any terms and conditions that by their nature or otherwise reasonably should -survive termination of this Agreement shall so be deemed to survive. Such -sections include especially the following: 1, 2, 6, 7, 9, 11, 12.4, 13 and 14. - -14.4. Entire Agreement - -This Agreement, the Appendices hereto, the License Certificate and any -applicable quote and Purchase Order accepted by The Qt Company constitute the -complete agreement between the Parties and supersedes all prior or -contemporaneous discussions, representations, and proposals, written or oral, -with respect to the subject matters discussed herein. - -In the event of any conflict or inconsistency between this Agreement and any -Purchase Order, the terms of this Agreement will prevail over the terms of the -Purchase Order with respect to such conflict or inconsistency. - -Parties specifically acknowledge and agree that this Agreement prevails over -any click-to-accept or similar agreements the Designated Users may need to -accept online upon download of the Licensed Software, as may be required by -The Qt Company's applicable processes relating to Licensed Software. - -14.5. Modifications - -No modification of this Agreement shall be effective unless contained in a -writing executed by an authorized representative of each Party. No term or -condition contained in Licensee's Purchase Order ("Deviating Terms") shall -apply unless The Qt Company has expressly agreed such Deviating Terms in -writing. Unless and to the extent expressly agreed by The Qt Company, any such -Deviating Terms shall be deemed void and with no legal effect. For clarity, -delivery of the Licensed Software following the receipt of the Purchase Order -including Deviating Terms shall not constitute acceptance of such Deviating -Terms. - -14.6. Force Majeure - -Except for the payment obligations hereunder, neither Party shall be liable to -the other for any delay or non-performance of its obligations hereunder in the -event and to the extent that such delay or non-performance is due to an event -of act of God, terrorist attack or other similar unforeseeable catastrophic -event that prevents either Party for fulfilling its obligations under this -Agreement and which such Party cannot avoid or circumvent ("Force Majeure -Event"). If the Force Majeure Event results in a delay or non-performance of a -Party for a period of three (3) months or longer, then either Party shall have -the right to terminate this Agreement with immediate effect without any -liability (except for the obligations of payment arising prior to the event of -Force Majeure) towards the other Party. - -14.7. Notices - -Any notice given by one Party to the other shall be deemed properly given and -deemed received if specifically acknowledged by the receiving Party in writing -or when successfully delivered to the recipient by hand, fax, or special -courier during normal business hours on a business day to the addresses -specified for each Party on the signature page. Each communication and document -made or delivered by one Party to the other Party pursuant to this Agreement -shall be in the English language. - -14.8. Export Control - -Licensee acknowledges that the Redistributables, as incorporated in -Applications or Devices, may be subject to export control restrictions under -the applicable laws of respective countries. Licensee shall fully comply with -all applicable export license restrictions and requirements as well as with all -laws and regulations relating to the Redistributables and exercise of licenses -hereunder and shall procure all necessary governmental authorizations, -including without limitation, all necessary licenses, approvals, permissions or -consents, where necessary for the re-exportation of the Redistributables, -Applications and/or Devices. - -14.9. No Implied License - -There are no implied licenses or other implied rights granted under this -Agreement, and all rights, save for those expressly granted hereunder, shall -remain with The Qt Company and its licensors. In addition, no licenses or -immunities are granted to the combination of the Licensed Software with any -other software or hardware not delivered by The Qt Company under this -Agreement. - -14.10. Attorney Fees - -The prevailing Party in any action to enforce this Agreement shall be entitled -to recover its attorney's fees and costs in connection with such action, as to -be ordered by the relevant dispute resolution body. - -14.11. Privacy - -Licensee acknowledges and agrees that for the purpose of this Agreement, -The Qt Company may collect, use, transfer and disclose personal data pertaining -to Designated Users as well as any other employees and directors of the -Licensee and its Contractors relevant for carrying out the intent of this -Agreement. Such personal data will be primarily collected from the relevant -individuals but may be collected also from Licensee (e.g. in the course of -Licensee's reporting obligations). The Parties acknowledge that as -The Qt Company determines the purpose and means for such collection and -processing of the applicable personal data, The Qt Company shall be regarded as -the Data Controller under the applicable Data Protection Legislation. -The Qt Company shall process any such personal data in accordance with its -privacy and security policies and practices, which will comply with all -applicable requirements of the Data Protection Legislation. - -14.12. Severability - -If any provision of this Agreement shall be adjudged by any court of competent -jurisdiction to be unenforceable or invalid, that provision shall be limited or -eliminated to the minimum extent necessary so that this Agreement shall -otherwise remain in full force and effect and enforceable. - -14.13. Marketing Rights - -Parties have agreed upon Marketing Rights pursuant to Appendix 7, if any. - - - - -APPENDICES -The Agreement includes following Appendices 1-10, as applicable. -- Appendix 1: Licensed Software details -- Appendix 2: Pricing -- Appendix 3: Add-on Software details (optional) -- Appendix 4: Small business and startup Licenses (optional) -- Appendix 5: Non-commercial and educational Licenses (optional) -- Appendix 6: License Reporting (optional) -- Appendix 7: Marketing Rights (optional) -- Appendix 8: Intentionally left blank (optional) -- Appendix 9: Support Terms -- Appendix 10: Conversion from legacy Licenses to Subscription (optional) -- Appendix 11: TERMS OF USE - QT INSIGHT TRACKER LIBRARY - - -APPENDIX 1: LICENSED SOFTWARE - -The modules and/or tools that are included in the latest publicly available -version of the respective product at the effective date of this Agreement- Qt -for Application Development Professional (ADP), Qt for Application Development -Enterprise (ADE), Qt for Device Creation Professional (DCP), Qt for Device -Creation Enterprise (DCE), - are marked with "X" in the below table. The -modules and tools are specific to each product version respectively and may -vary from version to version. Modules and tools included in the latest publicly -available version of the respective product at any given time are listed in -Appendix 1 of the latest version of this Agreement available at -www.qt.io/terms-conditions/. If a new version of Licensed Software does not -include a module or tool present in an older version which Licensee is entitled -to use under a valid license from The Qt Company, then Licensee will continue -to have such right during the Term of this Agreement. In the event a new -version of the Licensed Software adds modules or tools to any previous -version(s), Licensee's rights will extend to cover also such additional modules -and tools. - -Parts of the product that are permitted for distribution in object-code form -only ("Redistributables") are marked with "R" in the below table. - -+----------------------------------------------------------+ -| Modules / Tools | ADP | ADE | DCP | DCE | -+----------------------------------------------------------+ -| Active Qt | X,R | X,R | X,R | X,R | -+----------------------------------------------------------+ -| Qt 3D | X,R | X,R | X,R | X,R | -+----------------------------------------------------------+ -| Qt 5 Core Compatibility APIs | X,R | X,R | X,R | X,R | -+----------------------------------------------------------+ -| Qt Android Extras | X,R | X,R | X,R | X,R | -+----------------------------------------------------------+ -| Qt Bluetooth | X,R | X,R | X,R | X,R | -+----------------------------------------------------------+ -| Qt Canvas 3D | X,R | X,R | X,R | X,R | -+----------------------------------------------------------+ -| Qt Charts | X,R | X,R | X,R | X,R | -+----------------------------------------------------------+ -| Qt Concurrent | X,R | X,R | X,R | X,R | -+----------------------------------------------------------+ -| Qt Core | X,R | X,R | X,R | X,R | -+----------------------------------------------------------+ -| Qt Data Visualization | X,R | X,R | X,R | X,R | -+----------------------------------------------------------+ -| Qt D-Bus | X,R | X,R | X,R | X,R | -+----------------------------------------------------------+ -| Qt for Python | X,R | X,R | X,R | X,R | -+----------------------------------------------------------+ -| Qt for WebAssembly | X,R | X,R | X,R | X,R | -+----------------------------------------------------------+ -| Qt Gamepad | X,R | X,R | X,R | X,R | -+----------------------------------------------------------+ -| Qt Graphical Effects | X,R | X,R | X,R | X,R | -+----------------------------------------------------------+ -| Qt GUI | X,R | X,R | X,R | X,R | -+----------------------------------------------------------+ -| Qt Help | X,R | X,R | X,R | X,R | -+----------------------------------------------------------+ -| Qt Image Formats | X,R | X,R | X,R | X,R | -+----------------------------------------------------------+ -| Qt Location | X,R | X,R | X,R | X,R | -+----------------------------------------------------------+ -| Qt Lottie Animation | X,R | X,R | X,R | X,R | -+----------------------------------------------------------+ -| Qt Mac Extras | X,R | X,R | X,R | X,R | -+----------------------------------------------------------+ -| Qt Multimedia | X,R | X,R | X,R | X,R | -+----------------------------------------------------------+ -| Qt Multimedia Widgets | X,R | X,R | X,R | X,R | -+----------------------------------------------------------+ -| Qt Network | X,R | X,R | X,R | X,R | -+----------------------------------------------------------+ -| Qt Network Authorization | X,R | X,R | X,R | X,R | -+----------------------------------------------------------+ -| Qt NFC | X,R | X,R | X,R | X,R | -+----------------------------------------------------------+ -| Qt OpenGL | X,R | X,R | X,R | X,R | -+----------------------------------------------------------+ -| Qt PDF | X,R | X,R | X,R | X,R | -+----------------------------------------------------------+ -| Qt Platform Headers | X,R | X,R | X,R | X,R | -+----------------------------------------------------------+ -| Qt Positioning | X,R | X,R | X,R | X,R | -+----------------------------------------------------------+ -| Qt Print Support | X,R | X,R | X,R | X,R | -+----------------------------------------------------------+ -| Qt Purchasing | X,R | X,R | X,R | X,R | -+----------------------------------------------------------+ -| Qt QML | X,R | X,R | X,R | X,R | -+----------------------------------------------------------+ -| Qt Quick | X,R | X,R | X,R | X,R | -+----------------------------------------------------------+ -| Qt Quick 3D | X,R | X,R | X,R | X,R | -+----------------------------------------------------------+ -| Qt Quick Controls 1 | X,R | X,R | X,R | X,R | -+----------------------------------------------------------+ -| Qt Quick Controls | X,R | X,R | X,R | X,R | -+----------------------------------------------------------+ -| Qt Quick Dialogs | X,R | X,R | X,R | X,R | -+----------------------------------------------------------+ -| Qt Quick Extras | X,R | X,R | X,R | X,R | -+----------------------------------------------------------+ -| Qt Quick Layouts | X,R | X,R | X,R | X,R | -+----------------------------------------------------------+ -| Qt Quick Test | X,R | X,R | X,R | X,R | -+----------------------------------------------------------+ -| Qt Quick Timeline | X,R | X,R | X,R | X,R | -+----------------------------------------------------------+ -| Qt Quick WebGL | X,R | X,R | X,R | X,R | -+----------------------------------------------------------+ -| Qt Quick Widgets | X,R | X,R | X,R | X,R | -+----------------------------------------------------------+ -| Qt Remote Objects | X,R | X,R | X,R | X,R | -+----------------------------------------------------------+ -| Qt Script | X,R | X,R | X,R | X,R | -+----------------------------------------------------------+ -| Qt Script Tools | X,R | X,R | X,R | X,R | -+----------------------------------------------------------+ -| Qt SCXML | X,R | X,R | X,R | X,R | -+----------------------------------------------------------+ -| Qt Sensors | X,R | X,R | X,R | X,R | -+----------------------------------------------------------+ -| Qt Serial Bus | X,R | X,R | X,R | X,R | -+----------------------------------------------------------+ -| Qt Serial Port | X,R | X,R | X,R | X,R | -+----------------------------------------------------------+ -| Qt Shader Tools | X,R | X,R | X,R | X,R | -+----------------------------------------------------------+ -| Qt Speech | X,R | X,R | X,R | X,R | -+----------------------------------------------------------+ -| Qt State Machine | X,R | X,R | X,R | X,R | -+----------------------------------------------------------+ -| Qt SQL | X,R | X,R | X,R | X,R | -+----------------------------------------------------------+ -| Qt SVG | X,R | X,R | X,R | X,R | -+----------------------------------------------------------+ -| Qt Test | X,R | X,R | X,R | X,R | -+----------------------------------------------------------+ -| Qt UI Tools | X,R | X,R | X,R | X,R | -+----------------------------------------------------------+ -| Qt Virtual Keyboard | X,R | X,R | X,R | X,R | -+----------------------------------------------------------+ -| Qt Wayland Compositor | X,R | X,R | X,R | X,R | -+----------------------------------------------------------+ -| Qt WebChannel | X,R | X,R | X,R | X,R | -+----------------------------------------------------------+ -| Qt WebEngine | X,R | X,R | X,R | X,R | -+----------------------------------------------------------+ -| Qt WebSockets | X,R | X,R | X,R | X,R | -+----------------------------------------------------------+ -| Qt WebView | X,R | X,R | X,R | X,R | -+----------------------------------------------------------+ -| Qt Widgets | X,R | X,R | X,R | X,R | -+----------------------------------------------------------+ -| Qt Windows Extras | X,R | X,R | X,R | X,R | -+----------------------------------------------------------+ -| Qt X11 Extras | X,R | X,R | X,R | X,R | -+----------------------------------------------------------+ -| Qt XML | X,R | X,R | X,R | X,R | -+----------------------------------------------------------+ -| Qt XML Patterns | X,R | X,R | X,R | X,R | -+----------------------------------------------------------+ -| Qt Designer (Qt Widget Designer) | X,R | X,R | X,R | X,R | -+----------------------------------------------------------+ -| Qt Linguist | X,R | X,R | X,R | X,R | -+----------------------------------------------------------+ -| Qt Assistant | X,R | X,R | X,R | X,R | -+----------------------------------------------------------+ -| lupdate | X,R | X,R | X,R | X,R | -+----------------------------------------------------------+ -| lrelease | X,R | X,R | X,R | X,R | -+----------------------------------------------------------+ -| lconvert | X,R | X,R | X,R | X,R | -+----------------------------------------------------------+ -| Qt MQTT | | X,R | X,R | X,R | -+----------------------------------------------------------+ -| Qt KNX | | X,R | X,R | X,R | -+----------------------------------------------------------+ -| Qt OPC UA | | X,R | X,R | X,R | -+----------------------------------------------------------+ -| Qt CoAP | | X,R | X,R | X,R | -+----------------------------------------------------------+ -| Boot 2 Qt stacks | | | X,R | X,R | -+----------------------------------------------------------+ -| Qt OTA | | | X,R | X,R | -+----------------------------------------------------------+ -| Device Utilities | | | X,R | X,R | -+----------------------------------------------------------+ -| Qt Debugging Bridge (QDB) Daemon | | | X,R | X,R | -+----------------------------------------------------------+ -| Qt Quick Ultralite Controls | | | X,R | X,R | -+----------------------------------------------------------+ -| Qt Quick Ultralite | | | X,R | X,R | -+----------------------------------------------------------+ -| Qt Safe Renderer (QSR) | | | | X,R | -+----------------------------------------------------------+ -| Qt Application Manager | | | | X,R | -+----------------------------------------------------------+ -| Qt Interface Framework | | | | X,R | -+----------------------------------------------------------+ -| Neptune Reference UI | | | | X,R | -+----------------------------------------------------------+ -| Qt for Android Automotive (QAA) | | | | X,R | -+----------------------------------------------------------+ -| Qt Creator | X | X | X | X | -+----------------------------------------------------------+ -| Qt Design Studio Professional | X | X | X | X | -+----------------------------------------------------------+ -| androiddeployqt | X | X | X | X | -+----------------------------------------------------------+ -| androidtestrunner | X | X | X | X | -+----------------------------------------------------------+ -| canbusutil | X | X | X | X | -+----------------------------------------------------------+ -| dumpcpp | X | X | X | X | -+----------------------------------------------------------+ -| dumpdoc | X | X | X | X | -+----------------------------------------------------------+ -| fixqt4headers.pl | X | X | X | X | -+----------------------------------------------------------+ -| idc | X | X | X | X | -+----------------------------------------------------------+ -| moc | X | X | X | X | -+----------------------------------------------------------+ -| pixeltool | X | X | X | X | -+----------------------------------------------------------+ -| qdbus | X | X | X | X | -+----------------------------------------------------------+ -| qdbuscpp2xml | X | X | X | X | -+----------------------------------------------------------+ -| qdbusviwer | X | X | X | X | -+----------------------------------------------------------+ -| qdbusxml2cpp | X | X | X | X | -+----------------------------------------------------------+ -| qdistancefieldgenerator | X | X | X | X | -+----------------------------------------------------------+ -| qdoc | X | X | X | X | -+----------------------------------------------------------+ -| qhelpgenerator | X | X | X | X | -+----------------------------------------------------------+ -| qlalr | X | X | X | X | -+----------------------------------------------------------+ -| qmake | X | X | X | X | -+----------------------------------------------------------+ -| qml | X | X | X | X | -+----------------------------------------------------------+ -| qmlcachegen | X | X | X | X | -+----------------------------------------------------------+ -| qmldom | X | X | X | X | -+----------------------------------------------------------+ -| qmleasing | X | X | X | X | -+----------------------------------------------------------+ -| qmlformat | X | X | X | X | -+----------------------------------------------------------+ -| qmllint | X | X | X | X | -+----------------------------------------------------------+ -| qmlpreview | X | X | X | X | -+----------------------------------------------------------+ -| qmlprofiler | X | X | X | X | -+----------------------------------------------------------+ -| qmlscene | X | X | X | X | -+----------------------------------------------------------+ -| qmltestrunner | X | X | X | X | -+----------------------------------------------------------+ -| qmltime | X | X | X | X | -+----------------------------------------------------------+ -| qmlviewer | X | X | X | X | -+----------------------------------------------------------+ -| qtdiag | X | X | X | X | -+----------------------------------------------------------+ -| qtpaths | X | X | X | X | -+----------------------------------------------------------+ -| qtplugininfo | X | X | X | X | -+----------------------------------------------------------+ -| qvkgen | X | X | X | X | -+----------------------------------------------------------+ -| rcc | X | X | X | X | -+----------------------------------------------------------+ -| tracegen | X | X | X | X | -+----------------------------------------------------------+ -| uic | X | X | X | X | -+----------------------------------------------------------+ -| windeployqt | X | X | X | X | -+----------------------------------------------------------+ -| Target toolchains | | | X | X | -+----------------------------------------------------------+ -| Qt Debugging Bridge Host Tools | | | X | X | -+----------------------------------------------------------+ -| qtconfig-gui | | | X | X | -+----------------------------------------------------------+ -| Qt Emulator | | | X | X | -+----------------------------------------------------------+ -| Qt Creator VxWorks plugin | | | X | X | -+----------------------------------------------------------+ -| Qt Creator plugin for Qt | | | | X | -| Application Manager | | | | | -+----------------------------------------------------------+ -| qmlinterfacegenerator | | | | X | -+----------------------------------------------------------+ -| qmltocpp | | | | X | -+----------------------------------------------------------+ -| qulfontcompiler | | | | X | -+----------------------------------------------------------+ -| Qt Deployment Server | | | | X | -+----------------------------------------------------------+ - - -Rights for Application and Device use cases - -Following table summarizes the rights afforded by different products of the -Licensed Software to create and distribute Applications and Devices as defined -in this Agreement (X marks for rights): - -+---------------------------------------------------------------+ -| | Applications | Devices | -+---------------------------------------------------------------+ -| ADP | X | | -+---------------------------------------------------------------+ -| ADE | X | | -+---------------------------------------------------------------+ -| DCP | X | X | -+---------------------------------------------------------------+ -| DCE | X | X | -+---------------------------------------------------------------+ - -Licensed Software: Designer tools and modules - -The modules and/or tools that are included in the respective product - Qt for -Design Studio Professional (DSP), Qt for Design Studio Enterprise (DSE) - are -marked with "X" in the below table. - -Designer tools provides no Redistributables. - -+---------------------------------------------+ -| | DSP | DSE | -+---------------------------------------------+ -| Qt Design Studio | X | X | -+---------------------------------------------+ -| Qt Design Bridges | | X | -+---------------------------------------------+ -| QML Live on host | X | X | -+---------------------------------------------+ -| QML Live on target | | X | -+---------------------------------------------+ -| Variant Management | | X | -+---------------------------------------------+ -| Shader creation tools | | X | -+---------------------------------------------+ -| Profiling tools | | X | -+---------------------------------------------+ -| Simulink support | | X | -+---------------------------------------------+ - - -Both DSP and DSE can be used to create an user interface for use cases covered -by ADP, ADE, DCP and DCE. - -Licensed Software: QA Tools - -The modules and/or tools that are included in the respective QA Tools product -- Squish (both Tester and execution Licenses), Coco or Test Center - are marked -with "X" in the below table. Optional features that will need additional -licenses are marked with "O". QA Tools include no Redistributables. - -+---------------------------------------------------------------------+ -| | Squish | Coco | Test Center | -+---------------------------------------------------------------------+ -| Squish IDE | X | | | -+---------------------------------------------------------------------+ -| QA Tool-specific command line tools | X | X | X | -+---------------------------------------------------------------------+ -| Coverage Browser | | X | | -+---------------------------------------------------------------------+ -| HTML interface | | | X | -+---------------------------------------------------------------------+ -| Qt Support Module | X | | | -+---------------------------------------------------------------------+ -| Java support module | X | | | -+---------------------------------------------------------------------+ -| Windows support module | X | | | -+---------------------------------------------------------------------+ -| iOS support module | X | | | -+---------------------------------------------------------------------+ -| Android support module | X | | | -+---------------------------------------------------------------------+ -| Web support module | X | | | -+---------------------------------------------------------------------+ -| macOS support module | X | | | -+---------------------------------------------------------------------+ -| VNC support module | X | | | -+---------------------------------------------------------------------+ -| MCU support module | X | | | -+---------------------------------------------------------------------+ -| C and C++ language module | | X | | -+---------------------------------------------------------------------+ -| C# language module | | X | | -+---------------------------------------------------------------------+ -| QML language module | | X | | -+---------------------------------------------------------------------+ -| Tester Cross-Compilation Add-On | O | O | | -+---------------------------------------------------------------------+ - -License capabilities for Squish - -License capabilities that are included in the Squish Tester and Execution -Licenses are marked with "X" in the below table. - -+-----------------------------------------------------------------------------+ -| | Squish Tester License | Squish Execution License | -+-----------------------------------------------------------------------------+ -| Ability to create, edit, | X | | -| and debug test cas | | | -+-----------------------------------------------------------------------------+ -| Ability to execute test | X | X | -| cases | | | -+-----------------------------------------------------------------------------+ - -Install and use capabilities for QA Tools - -Install and use capabilities that are included in the respective QA Tools -products are defined in the below table. - -+-----------------------------------------------------------------------------+ -| | Squish | Squish | Coco | Test | -| | Tester | Execution | License | Center | -| | License | License | | License | -+-----------------------------------------------------------------------------+ -| Number of installation | Unlimited | Unlimited | Unlimited | One(1) | -| instances per license | | | | | -+-----------------------------------------------------------------------------+ -| Number of concurrent | Limited by| Limited by | Limited by | Limited by | -| users | number of | number of | number of | number of | -| | Squish | Squish | Coco | Test Center | -| | Tester | Execution | Tester | Licenses | -| | Licenses | Licenses | Licenses | | -+-----------------------------------------------------------------------------+ - - -APPENDIX 2: PRICING - -Separate template - -APPENDIX 3: ADD-ON PRODUCTS TO LICENSED SOFTWARE - -Intentionally left blank. - -APPENDIX 4: SMALL BUSINESS AND STARTUP - -The provisions of this Appendix 4 are applicable for companies with an annual -revenue, including funding, equivalent to maximum of 250,000 USD (in applicable -currency) during the latest full calendar year, as evidenced by duly audited -records of the Licensee and approved by The Qt Company ("Start-up Company"). - -Start-up Companies are qualified for a discounted License Fee for maximum of -four (4) Development Licenses ("Start-up Development License") unless otherwise -agreed between the parties. - -Start-up Development License entitles the respective Designated User for -Support only for Install Support as defined in Appendix 9, Support Terms. - -Upon expiry of the respective Development License Term, the Start-up -Development Licenses shall be automatically extended, pursuant to Section 3.1 -of the Agreement, for a Renewal Term either as new Start-up Development -Licenses (if the Licensee still qualifies as a Start-up Company), or as normal -then standard list price Development Licenses (if the Licensee no longer -qualifies as a Start-up Company). - -APPENDIX 5: NON-COMMERCIAL AND EDUCATIONAL USE - -The provisions of this Appendix 5 are applicable for non-commercial use of the -Licensed Software by the Licensee. - -For the purpose of this Appendix 5, the following additional definitions -(replacing the relevant definition of the Agreement, where applicable) shall be -applicable: - -"Demo Units" shall mean (i) hardware development platform, which incorporates -the Licensed Software along with Licensee's software and/or hardware, and (ii) -prototype versions of Applications or Devices. - -"Designated User(s)" shall mean the employees and students of the Licensee. - -"Licensee Products" shall mean Applications and/or Devices. - -"Permitted Purpose" shall mean (i) Licensee's internal evaluation and testing -of Licensed Software, (ii) building Demo Units as well as (iii) educational -use. - -"Agreement Term" shall mean a period of twelve (12) months or any such other -period as may be agreed between the Parties. - -For the purpose of this Appendix 5, the following changes shall be agreed with -respect to relevant Sections of the Agreement: - I. Recital (A) shall be replaced in its entirety to read as follows: - "(A) Licensee wishes to use the Licensed Software for the Permitted - Purpose." - II. Section 3.1 shall be replaced in its entirety to read as follows: - "The Qt Company grants to Licensee a personal, non-exclusive, - non-transferable, revocable, royalty-free license, valid for the - Agreement Term, to use, modify and copy the Licensed Software solely - for the Permitted Purpose. Licensee may install copies of the Licensed - Software on five (5) computers per Designated User, provided that only - the Designated Users who have a valid Development License may use the - Licensed Software. Licensee may demonstrate the Demo Units, provided - that such demonstrations must be conducted by Licensee, and the Demo - Units must remain in Licensee's possession and under Licensee's control - at all times. - For clarity, this Agreement does not (i) entitle Licensee to use - Licensed Software to create Applications or Devices (other than - prototypes thereof) or (ii) carry any distribution rights to Licensee, - but such rights are subject to and conditional upon conclusion of a - separate license agreement with The Qt Company." - III. Sections 3.2, 3.3, 3.5, 3.6, 8 and 10 shall be deleted. - IV. Section 3.4 shall be replaced in its entirety to read as follows: - "Licensee shall not: - - remove or alter any copyright, trademark or other proprietary rights - notice contained in any portion of the Licensed Software; - - transfer, publish, sublicense, disclose, display or otherwise make - the Licensed Software available to any third party (except that - Licensee may demonstrate the Demo Units pursuant to Section 3.1); - - in any way combine, incorporate or integrate Licensed Software with, - or use Licensed Software for creation of, any software created with - or incorporating Open Source Qt; Licensee shall cause all Designated - Users who make use of the licenses granted under this Agreement, to - be contractually bound to comply with the relevant terms of this - Agreement and not to use the Licensed Software beyond the terms - hereof. Licensee shall be responsible for any and all actions and - omissions of its Designated Users relating to the Licensed Software - and use thereof. Any use of Licensed Software beyond the provisions - of this Agreement is strictly prohibited and requires an additional - license from The Qt Company." - V. Section 12 shall be replaced in its entirety to read as follows: - "This Agreement shall enter into force upon due acceptance by both - Parties and remain in force for the Agreement Term, unless and until - terminated pursuant to the terms of Section 12. - Upon termination of the Agreement, Licensee shall cease using the - Licensed Software. All other copies of Licensed Software in the - possession or control of Licensee must be erased or destroyed. An - officer of Licensee must, upon request, promptly deliver to The Qt - Company a written confirmation that this has occurred." - -Except for the modifications specified above, this Appendix carries no change -to the terms of the Agreement which shall remain in full force. - -APPENDIX 6: LICENSE REPORTING - -Separate template - -APPENDIX 7: MARKETING RIGHTS - -This Appendix 7 has the purpose to grant visibility through The Qt Company -marketing channels of the usage of Qt and related product and service in -Licensee product. Following related marketing right are agreed between the Qt -Company and the Licensee. - -1. LICENSEE NAME AND LICENSEE LOGO - -The Qt Company has the right to use Licensee name and Licensee logo in public -channel, in respect of the value proposition that the Qt company provided to -the Licensee. - -2. MARKETING CONTENT COOPERATION - -2.1. LICENSEE CASES - -The Licensee is open to collaborate on content creation for marketing and -communication purpose. The Licensee will nominate one responsible that will be -in charge to support The Qt company with this content creation, according to -content format paragraph, answering technical questions or sharing professional -picture or video of required content. The Qt Company will have the right to -advertise this in Content Format and Channel as mentioned in paragraph 3 and 4. - -2.2. FINAL PRODUCT REFERRAL - -Licensee agree that The Qt Company could connect their software product and -services with the Licensee device or application, that the Licensee has created -using The Qt Company technology and competence. Licensee will provide high -quality picture, and video of the created final product where the Qt technology -is running into. The Qt Company will have the right to advertise this in -Content Format and Channel as mentioned in paragraph 3 and 4. - -3. CONTENT FORMAT - -- Video -- Written Licensee case -- Press release -- Social media posts -- Emails -- Event booth Graphics -- Printed material - -4. CHANNELS - -- Social media -- The Qt Company resource center and website -- Email to the Qt company contact database -- Events -- Online webinars -- Public speech -- Public presentations - -APPENDIX 8: INTENTIONALLY LEFT BLANK - -APPENDIX 9: SUPPORT TERMS -Version 2023-04 - -These Qt support terms and conditions (“Support Terms”) set forth the legal -framework, where under The Qt Company (“The Qt Company”) provides support -services (as herein defined) to the Licensee. - -1 DEFINITIONS - -“Application Code” shall mean a computer software program written strictly -using the Qt programming language, by or for the Licensee, with a user -interface, enabling the Licensee or their users to accomplish a specific task -and display any results of the task on the display monitor or screen. - -“Customer Portal” shall mean The Qt Company’s web-based service and support -user interface located at https://account.qt.io/ or at another location -designated by The Qt Company. Customer Portal is used by a Designated User with -Qt Account, and it provides downloads, license management, license certificate -and other services for Designated Users. - -“Dedicated Contact” shall mean the employee of The Qt Company who will be the -first point of contact for all Designated Users’ requests for Support. - -“Errors” shall mean an error, flaw, mistake, failure, or fault in Licensed -Software that prevents it from behaving as described in the relevant -documentation or as agreed between the Parties. Designated User can follow the -state and progress of Errors in Customer Portal. - -“Extended Support” shall mean a continuation to the normal Support period, -which allows Designated Users to receive selected Support (Standard Support or -Premium Support) for a version of Licensed Software that is no longer generally -supported by The Qt Company. - -”Install Support” shall mean Support that is limited to installation-related -Error(s) on Development Platforms specified as supported host platforms for -each Qt release under doc.qt.io. Install Support covers also operational use of -the QA Tools, but not operational use of Qt Software. - -“Maintenance Release” shall mean a release or version of Licensed Software -containing bug fixes, error corrections and other changes targeted to -maintaining and improving product stability and quality. Maintenance Releases -are generally depicted as a change to the third digit of Licensed Software -version number. - -“Platforms” shall mean both Development Platforms and Deployment Platforms. -Supported host and target Platforms may vary from for each Qt release as -defined under doc.qt.io. - -“Premium Support” shall mean an upgraded level of Support that The Qt Company -provides pursuant to these Support Terms to Licensee if Licensee has purchased -Premium Support instead of Standard Support. Premium Support also covers what -is included in Standard Support. Premium Support shall always be purchased for -all Designated User(s) in the respective development team of the Licensee. - -“Qt Account” shall mean the Qt Account for a Designated User used for using Qt -services and Customer Portal. A Qt Account is mapped to the Licensee company -with the corporate email domain or domains. - -”Qualification Kit” shall mean a set of documents and validation test cases -used for product certification needs as defined in section 2.6. - -“Response Time” shall mean the period of time from when Licensee notifies The -Qt Company about an Error or requests Support until The Qt Company provides -Licensee with a response that addresses (but not necessarily resolves) the -reported Error or provides the requested Support. - -“Standard Support” shall mean standard level of Support that The Qt Company -provides pursuant to these Support Terms to Licensee. Standard Support also -covers what is included in Install Support. - -“Security Issue” shall mean an Error that may cause a vulnerability in a system -or application that uses the Licensed Software. - -“Support” shall mean developer assistance that is provided by The Qt Company to -assist eligible Designated Users in Licensed Software installation, usage and -functionality problem resolution for Error(s) and Error workarounds pursuant to -the terms of these Support Terms. Support for different products is available -as specified in the below table (‘X’ marking the Support that is included in -the license price, optional Add-on Support services are marked as ‘O’): - -+-----------------------------------------------------------------------------+ -| | Install| Standard| Premium| Extended| Qualification| -| | Support| Support | Support| Support | Kit | -+-----------------------------------------------------------------------------+ -| DSP | X | X | O | O | | -+-----------------------------------------------------------------------------+ -| DSE | X | X | O | O | | -+-----------------------------------------------------------------------------+ -| ADP | X | | | | | -+-----------------------------------------------------------------------------+ -| ADE | X | X | O | O | | -+-----------------------------------------------------------------------------+ -| DCP | X | X | O | O | | -+-----------------------------------------------------------------------------+ -| DCE | X | X | O | O | | -+-----------------------------------------------------------------------------+ -| Squish | X | X | O | | O | -+-----------------------------------------------------------------------------+ -| Coco | X | X | O | | O | -+-----------------------------------------------------------------------------+ -| Test Center | X | X | O | | O | -+-----------------------------------------------------------------------------+ -| Axivion Suite | X | X | | | | -+-----------------------------------------------------------------------------+ -| Architecture Analysis | X | X | | | | -+-----------------------------------------------------------------------------+ -| Static Code Analysis | X | X | | | | -+-----------------------------------------------------------------------------+ -| Static Coverage | X | X | | | | -| Analysis Professional | | | | | | -+-----------------------------------------------------------------------------+ -| Qt Insight | | X | | | | -+-----------------------------------------------------------------------------+ - -“Support Validity Term” shall mean the Development License Term or any other -fixed time period agreed between the Parties during which time the Licensee is -eligible to receive Support from The Qt Company. - -2 SUPPORT SERVICES - -2.1 Support Services Provided by The Qt Company - -Subject to these Support Terms and during the Support Validity Term, The Qt -Company will via its Customer Portal, provide Designated User(s) with Support -for the Licensed Software which Licensee has licensed under the Agreement. The -Qt Company will make commercially reasonable efforts to solve any Errors -reported by Designated User(s). Resolution of an Error may be provided through -Designated User(s) themselves downloading of a later released version of the -applicable Licensed Software product(s) or providing the Designated User with a -workaround addressing such Error or providing the Designated User with an -updated tool configuration. - -2.2 Licensee's Obligations - -To report an Error, the Designated User shall register the Error on the -Customer Portal. If the Designated User considers the reported Error to be a -Security Issue, the Error shall be marked as a Security Issue. - -The Designated User must provide adequate information and documentation to The -Qt Company to enable it to recreate the Error or problem for which the -Designated User has sought assistance. To ensure efficient handling of Errors, -the Designated User must provide the following information, where relevant: -- A clear, detailed description of the problem, question or suggestion; -- Identification of which Licensed Software product and version is affected; -- Identification of the operating environment (e.g. operating system, hardware - Platform, build tools, tool configuration, etc.) on which the problem exists; -- Marking the issue as a Security Issue, when reporting a Security Issue; -- On Standard Support: A complete and compilable test case of not more than 500 - lines of code that demonstrates the problem; -- On Premium Support: A complete and compilable test case that demonstrates the - problem or access to Application Code source codes. - -Additional relevant content, such as screenshots, etc. -Additional content should be included as attachments. The preferred image -formats are JPEG and PNG. Compressed content should be included in zip or -tar.gz archives. Executable content and documents in platform specific formats -such as Microsoft Office' are not accepted. - -In order for The Qt Company to provide prompt handling of Errors, the -Designated User shall promptly respond to any requests from The Qt Company for -additional information. - -2.3 Support Limitations - -General limitations: - -Each version or release of the Licensed Software will be Supported under -Standard Support or Premium Support only for limited time period as set forth -in doc.qt.io or in documentation provided with the respective Licensed Software -product. If nothing is documented, a release of Licensed Software is supported -for one (1) year from the release date of the version x.y.0 and Long Term -Support (LTS) Releases are supported for a period of three (3) years from the -release date of the LTS version x.y.0. - -The Qt Company shall only provide Support for Designated User(s) through -Customer Portal.Support is made available for the entire development teams -only: It is not allowed to purchase Support only for some members of the -development team, and all Designated Users of the respective development team -must be eligible for the same level of Support. - -Support is not provided for snapshots, preview releases, beta releases or -release candidates. - -The Qt Company shall have no obligation to provide Support for 3rd party -components, hardware or operating system specific problems or problems arising -from improper use, accident, neglect, or modification of Qt. - -Limitations with Install Support: - -Support limited to (i) Error(s) regarding installation and setting up of the Qt -development environment on host Platforms, or (ii) Errors impacting operational -use of the QA Tools. - -Limitations with Standard Support: - -The Qt Company shall not provide Support for third-party software or problems -caused by third-party software even if such third-party software is distributed -together with Licensed Software product(s). - -The Qt Company shall only provide Support for Error(s) that are reported on and -can be reproduced on Platforms that are officially supported for the release of -the Licensed Software. - -Limitations with Premium support: - -The Qt Company shall not provide Support for third-party software or problems -caused by third-party software. However, if such third-party software is -distributed together with Licensed Software, The Qt Company will make -commercially reasonable efforts to solve such problems. - -The Qt Company shall only provide Support for Error(s) that can be reproduced -on Platforms that are officially supported for the release of the Licensed -Software. If the Error is on a Platform that is not supported, The Qt Company -will make commercially reasonable efforts to provide a solution on closest -corresponding supported Platform. - -Premium Support is optional and purchased for an agreed bucket of hours -(“Bucket”). Hours can be used by any Designated User in the respective -development team. To encourage continuous usage of the Support, ten percent -(10%) of the purchased Bucket shall automatically expire (regardless of whether -such support hours are actually used or not by the Licensee) each month after -three (3) months from the purchase of the Premium Support. - -2.4 Handling of Security Issues - -The reported Errors marked as Security Issues will be assessed by experts to -determine the severity of the issue and to verify if it indeed is a valid -Security Issue. The Designated User who reported the issue may be contacted for -more details. If the reported issue is not deemed to be a Security Issue, it -will be treated as a normal Error and handled accordingly. - -A verified Security Issue will be fixed as soon as possible. Qt Company will -notify all Licensees via appropriate channels about the Security Errors and -availability of patches for Licensed Software. Typically, a fix for the -Security Issue is included in the next Maintenance Release of Licensed -Software. - -If the Security Issue is reported in a third-party library used in Licensed -Software, The Qt Company will notify the relevant third party of such Security -Issue detected in their library. When the Security Issue is fixed in the -third-party library, the new version of the third-party library will be in the -next feasible Maintenance Release of the Licensed Software. If a fixed version -of the third-party library is not available, The Qt Company may instead decide -to include documentation regarding the issue, or a patch for this third-party -library. - -All known Security Issues in Licensed Software will be mentioned as part of the -change notes released with each version of Licensed Software. - -2.5 Extended Support - -Extended Support extends the Support Validity Term for a release of Licensed -Software that is no longer generally supported. - -Extended Support includes and is by default provided with Standard Support -rules and limitations, unless Extended Support is purchased together with -Premium Support in which case Premium Support rules and limitations will apply. - -Extended Support is optional and purchased with annual fee and separately per -each Licensee product. Extended Support will need definition of (i) Licensee -product, (ii) used Platform(s) and (iii) Licensed Software version(s). For -avoidance of doubt, Extended Support requires that the Designated User has a -valid license for the respective Licensed Software. - -2.6 Qualification Kit - -The Qt Company shall provide a set of documents and validation tests that -enable the Licensee to qualify QA testing tool (subject to a separate fee) or -Qt Safe Renderer for the purpose of safety certification of Licensee end-to-end -solution. Exact complied safety standards may vary between products, used -features, use case, and industry. - -3 RESPONSE TIME - -In performing Support, The Qt Company shall commit to following, non-binding, -Response Times: - -Standard Support: Errors and Support requests will have a Response Time not to -exceed two (2) business days. - -Premium Support: Errors and Support requests will have a Response Time not to -exceed one (1) business day. - -Security Issues: Errors that are Security Issues will have a Response Time not -to exceed one (1) business day. - -For complex issues, The Qt Company may provide an initial response to the -Designated User and then follow up, without undue delay, with additional -communication before an Error is properly addressed or Support provided. - -4 ADDITIONAL SERVICES IN PREMIUM SUPPORT - -The Designated User(s) will be assigned a Dedicated Contact to handle requests -for Support. Dedicated Contact is subject to change in cases such as sick -leave, vacation and other similar reasons. - -The Designated User(s) can on request ask The Qt Company to access their -computer remotely in order to resolve problems directly. - -The Designated User(s) can request a session via Instant Messaging or phone -call in the support request to The Qt Company. - -Premium Support can assist Licensee in implementing new features, bug fixes -and accessing patches in Licensed Software or Application Code. - -All Support requests will be handled with high priority. - -5 MAINTENANCE RELEASES, UPDATES AND UPGRADES - -Under the Support the Licensee is eligible for Maintenance Releases and Updates -that The Qt Company generally makes available to customers who has purchased -Support. Unless otherwise decided by The Company at its free and absolute -discretion, Upgrades will not be provided under the Support. - -The primary focus of Maintenance Releases is product quality. Therefore, each -Maintenance Release typically includes the following types of changes to the -previous version of Licensed Software: -- Bug fixes caused by changes to previously working code; -- Fixes related to build issues on supported Platforms; -- Error corrections specific to a single Platform that are not present on other - Platforms; -- Corrections to Security Issues; -- Critical Error corrections such as crashes, data corruption, loss of data, - race conditions; and -- Updates to documentation and license information when deemed necessary by - The Qt Company. - -The primary focus of Updates is introducing new features to Licensed Software -and covering new platforms. Therefore, each Updates typically includes the -following types of changes to the previous version of Licensed Software: -- New platform support; -- New toolchain support; -- New features and Qt modules; - -6 WARRANTY DISCLAIMER - -The Qt Company makes no warranties that the Support provided will be successful -in resolving any difficulties or problems or in diagnosing faults reported by -Licensee. Support is provided to Licensee on an "as is" basis. To the maximum -extent permitted by applicable law, The Qt Company disclaims all warranties and -conditions, either express or implied, including, but not limited to, implied -warranties of merchantability and fitness for a particular purpose for the -Support provided by The Qt Company to Licensee. - -APPENDIX 10: CONVERSION TO SUBSCRIPTION - -Subject to the terms of this Appendix Licensee's current development licenses -("Current Licenses") for commercial version of Qt Software and the license -agreements governing such Current Licenses ("Existing Agreements") are being -replaced by this Agreement and subscription based Development Licenses -governed hereunder, as further specified below. - -+---------------------------------------------------------------------------+ -| Existing Agreement(s) | and | -| signing parties, version | | -| thereof | | -+---------------------------------------------------------------------------+ - -Parties hereby agree on conversion of Current Licenses listed in attached -Exhibit A to the subscription licenses listed in attached Exhibit B for use -through License Term. As of the date hereof, - -i. Licensee's Current Licenses as listed in Exhibit A shall terminate and be -replaced with the Subscription licenses listed in Exhibit B and; -ii. Existing Agreements are terminated. - -Prices for the conversion of Current Licenses are defined in Appendix 2 -Pricing or Quote. - -Notwithstanding anything in this Appendix to the contrary, and in addition to -any payments due pursuant to this Appendix, Licensee remains fully obligated to -fulfill any and all outstanding payment obligations to The Qt Company under any -applicable Existing Agreements. For the avoidance of doubt, if any payments -remain outstanding on the Current Licenses under the applicable terms Licensee -will continue to make such payments in accordance with the applicable order -documentation, notwithstanding the fact that the Current Licenses are being -converted to Development Licenses pursuant to this Appendix. - -APPENDIX 11: TERMS OF USE - QT INSIGHT TRACKER LIBRARY -Version 1.0 - -Qt Insight Tracker Library ("Tracker Library") is a software module used to -collect end user data from Customer's Application and Devices relating to The -Qt Company's Qt Insight online service the Customer is ordering from The Qt -Company under a separate service agreement ("Service Agreement"). - -Unless otherwise set forth herein, definitions written in capital letters used -herein shall have the meaning set forth in the Service Agreement. - -Subject to these terms The Qt Company grants to Customer a worldwide, -non-exclusive, non-transferable, royalty-free, revocable (for cause) right and -license, valid for the term of the Service Agreement, to - (i) use, copy and modify Tracker Library for the purpose of including it - into the Devices and Applications and solely for the purpose of being - used only in conjunction with Insight Cloud or Insight Private Cloud, - and - (ii) distribute, by itself or through its Contractors, Tracker Library as - installed, incorporated, or integrated into Applications and/or - Devices. - -Use of Tracker Library in a way or for the purpose other than the above is -strictly prohibited. Tracker Library is licensed to the Customer in all -respects "as is". - -TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW, THE QT COMPANY ON BEHALF OF -ITSELF AND ITS LICENSORS, SUPPLIERS AND AFFILIATES, DISCLAIMS ALL OTHER -WARRANTIES, EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, ANY IMPLIED -WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND -NON-INFRINGEMENT WITH REGARD TO THE TRACKER LIBRARY. THE QT COMPANY DOES NOT -WARRANT THAT THE TRACKER LIBRARY WILL SATISFY CUSTOMER'S REQUIREMENTS OR THAT -IT WILL OPERATE WITHOUT DEFECT OR ERROR OR THAT THE OPERATION THEREOF WILL BE -UNINTERRUPTED. -IN NO EVENT SHALL THE QT COMPANY BE LIABLE TO THE CUSTOMER FOR ANY LOSS OF -PROFIT, LOSS OF DATA, LOSS OF BUSINESS OR GOODWILL OR ANY OTHER INDIRECT, -SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE COST, DAMAGES OR EXPENSE OF ANY -KIND, HOWSOEVER ARISING UNDER OR IN CONNECTION WITH THE USE OF THE TRACKER -LIBRARY. - -THE TOTAL MAXIMUM LIABILITY OF THE QT COMPANY UNDER THESE TERMS SHALL IN NO -EVENT EXCEED 10,000 EUROS. +Qt Frame Agreement +Version 2024-02 + +1. PARTIES OF THIS AGREEMENT +1.1. This Qt Frame Agreement—comprised of these general terms together with the appendices attached hereto, (hereinafter “Agreement”) is made by and between: The Qt Company, as defined below (hereinafter ”The Qt Company”) AND Customer name (hereinafter “Customer"):___________________ Business Id (e.g. VAT or EIN number):___________________ +1.2. The parties above are hereinafter individually referred to as a "Party" and collectively as the "Parties". + +2. STRUCTURE AND OBJECT OF THE AGREEMENT +2.1. The Parties have entered into this Agreement to agree on the terms and conditions applicable to The Qt Company's delivery of products and services ("Services") to Customer. +2.2. This Agreement is comprised of the following components: +(i) This Agreement, which contains the general terms applicable to all Services, +(ii) Appendices for each of the Services, containing terms applicable to that individual set of Services ("Service Terms"), +(iii) a Qt Appendix for Pricing, if applicable, which contains pricing for specific Services, and +(iv) other topic-specific appendices, such as Support or Marketing Rights. +2.3. Any and all Services purchased shall be specified in, and agreed upon between, the Parties under a separate purchase order, statement of work, quote, or similar document ("Purchase Document"). Each Purchase Document concluded under this Agreement shall include a reference to this Agreement and be governed by this Agreement. + +3. DEFINITIONS +3.1. "Affiliate" of a Party shall mean an entity (i) which is directly or indirectly controlling such Party; (ii) which is under the same direct or indirect ownership or control as such Party; or (iii) which is directly or indirectly owned or controlled by such Party. For these purposes, an entity shall be treated as being controlled by another if that other entity has fifty percent (50 %) or more of the votes in such entity, is able to direct its affairs and/or to control the composition of its board of directors or equivalent body. +3.2. "Contractor" shall mean third-party consultants, distributors and contractors performing services to Customer under an applicable contractual arrangement. +3.3. "Customer" shall mean the individual or legal entity specified in Section 1 above, that is a Party to this Agreement. +3.4. "Force Majeure Event" shall have the meaning set forth in Section 11.7. +3.5. "Licensed Software" shall mean The Qt Company's commercial software product which is licensed for use by Customer under this Agreement and corresponding Service Terms. Licensed Software shall include, if and to the extent applicable and specified in the applicable relevant Service Terms, corresponding online or electronic documentation, associated media and printed materials, including the source code, and example programs. The Qt Company may in the course of its development activities, at its free and absolute discretion and without any obligation to send or publish any notifications to Customer or in general, make changes, additions or deletions in the components and functionalities of the Licensed Software, provided that no such changes, additions or deletions will affect the already released version of the Licensed Software, but only upcoming version(s). Licensed Software is commercial computer software, developed at private expense and offered to the public under standard commercial terms. +3.6. "Professional Services" shall mean The Qt Company's professional-, consulting-, training- and/or project services delivered to Customer under this Agreement and specified in a Purchase Document. +3.7. "Support" shall mean maintenance and support services provided by The Qt Company to assist Customer in using the Licensed Software, as further specified in the Appendix for Support Terms. +3.8. "The Qt Company" shall mean: +(i) in the event Customer is an entity residing in the United States or a legal entity incorporated in or having its headquarters in the United States, The Qt Company Inc., a Delaware corporation with its office at 3031 Tisch Way, 110 Plaza West, San Jose, CA 95128, USA.; or +(ii) in the event Customer is an entity residing outside of the United States or a legal entity incorporated or having its registered office outside of the United States, The Qt Company Oy., a Finnish company with its registered office at Miestentie 7, 02150 Espoo, Finland. + +4. PRICES AND PAYMENT +4.1. The Qt Company agrees to make Services available to Customer subject to the prices set forth in the Appendix for Pricing. In the event that the Appendix for Pricing does not include a price for certain Services, the applicable price shall be the price agreed by the Parties in the respective Purchase Document. +4.2. All prices are exclusive of value added tax or other taxes, levels, or duties. Value added tax as well as other possible public charges imposed by authorities shall be added to the prices. +4.3. All fees under this Agreement are non-cancellable and non-refundable. +4.4. All fees under this Agreement shall be paid by Customer no later than thirty (30) days from the date of the applicable invoice from The Qt Company. +4.5. Unless otherwise agreed or provided in the respective Service Terms or Purchase Document, The Qt Company will invoice fees for: +4.5.1. Licensed Software and Support in advance upon conclusion of the Purchase Document, and +4.5.2. Professional Services monthly in arrears after the Service has been performed. +4.6. A late payment charge of the lower of: (a) one percent (1%) per month; or (b) the highest interest rate stipulated by applicable law, shall be charged on any unpaid balances that remain past due and which have not been disputed by Customer in good faith within thirty (30) days of receipt of invoice from The Qt Company. +4.7. The Qt Company may either (i) invoice Customer based on existing agreement, (ii) request Customer to place a purchase order corresponding to a quote by The Qt Company, or (iii) use Customer's stored Credit Card information to automatically charge the Customer for the relevant Renewal Term. +4.8. Unless and to the extent otherwise agreed in the Appendix for Pricing or in the Purchase Document, The Qt Company shall be entitled to adjust the prices set forth in the Appendix for Pricing by notifying Customer of the change in writing at least sixty (60) days before the effective date of the change. The change shall not affect the current pricing term of Services agreed upon before the effective date of the change. + +5. CONFIDENTIALITY +5.1. The Parties shall keep confidential, and shall not use or disclose to any unauthorized third parties, the existence and content of this Agreement as well as any Confidential Information received from the other Party or otherwise learned in connection with the Agreement or the performance of the Services, without the prior written consent of the other Party. Confidential Information shall mean information that is designated as confidential or that would be reasonably understood to be confidential given the circumstances of disclosure and the nature of the information. The Parties shall not use Confidential Information received from the other Party for any other purposes than the performance of the Agreement or the fulfilment of their rights and obligations hereunder. +5.2. Each Party shall limit access the other Party's Confidential Information only to those of its employees, subcontractors, Contractors, Affiliates or financial or legal advisors who necessarily need access to the Confidential Information for the proper performance of the Party's rights and obligations under the Agreement. Each Party shall ensure that the persons receiving Confidential Information of the other Party are bound by confidentiality obligations not less restrictive than those stipulated herein. +5.3. Each Party shall protect the confidentiality of the other Party's Confidential Information with at least the same degree of security as it exercises to its own confidential information, but no less than a standard of reasonable care. +5.4. The confidentiality obligation stipulated herein shall not be applied to material and information which: +(iii) has become generally available or otherwise public prior to its submission by the other Party; +(iv) becomes generally available or otherwise public due to a reason other than the negligence or omission of the recipient or its personnel or other actions in violation of this Agreement or applicable legislation; +(v) the Party has lawfully received from a third party without any obligation of confidentiality; +(vi) was lawfully in the possession of the receiving Party prior to receipt of the same from the other Party without any obligation of confidentiality related thereto; +(vii) a Party has developed independently without using material or information received from the other Party; or +(viii) a Party must disclose pursuant to law, decree or other order issued by competent regulatory or governmental body or other public authority or a judicial order, in which case the Party shall, to the extent permitted by applicable law, inform the other Party in writing of the disclosure of information prior to such disclosure. +5.5. Each Party shall, upon request of the other Party at any time, including upon termination, cancellation or expiry of the Agreement, promptly destroy or deliver to the other Party any and all the documents, files, copies and material containing Confidential Information of the other Party. Notwithstanding the foregoing, a Party may retain one copy of the Confidential Information in a secure location, if and solely to the extent required to comply with applicable laws or regulations. Any Confidential Information stored in electronic back-up form shall be rendered inaccessible and destroyed in accordance with standard back-up procedures. + +6. INTELLECTUAL PROPERTY RIGHTS +6.1. Unless and to the extent expressly provided in the respective Service Terms, this Agreement carries no assignment or license to the intellectual property rights of either Party and all such rights are and shall remain the exclusive property of the Party to whom such rights are vested under applicable law at the signing of this Agreement or thereafter. +6.2. Where The Qt Company's delivery includes any materials owned by a third party, such third party materials shall be governed in all respects by the applicable license terms of such third-party right holders. The Qt Company shall duly inform the Customer whenever such third party materials are included in the Services and of applicable license terms to be followed by the Customer in using such third party materials. + +7. FEES AND ORDERING +7.1. Services Fees. Services Fees are described in the Purchase Document. +7.2. Ordering Services. +(i) Customer may purchase Services pursuant to agreed pricing terms or, if no specific pricing terms have been agreed upon, at The Qt Company's standard pricing terms applicable at the time of purchase. +(ii) Unless expressly otherwise agreed, any price or other term quoted to Customer shall only be valid for the thirty (30) days from the date such price has been quoted. + +8. LIMITED WARRANTY AND WARRANTY DISCLAIMER +8.1. The Qt Company hereby represents and warrants that: (i) it has the power and authority to grant the rights and licenses granted to Customer under this Agreement; (ii) the Licensed Software will operate materially in accordance with its specifications (as set forth in the applicable product documentation or, where relevant, program description); (iii) Professional Services and Support will be performed in a professional, workmanlike manner pursuant to the Agreement; and (iv) during the ten years prior to the effective date of this Agreement, there have not been any claims alleging that the Licensed Software has infringed any intellectual property rights of a third party and, to the knowledge of The Qt Company as of the effective date of this Agreement, no such infringement exists. These warranties do not apply to issues arising from, or relating to, any third-party materials or Customer's use of the Licensed Software in violation of applicable law or the terms of this Agreement. +8.2. Except to the extent set forth above, the Services are delivered to Customer "as is" and to the maximum extent permitted by applicable law, exclusive of other warranties, whether express, implied, or otherwise. Customer's sole and exclusive remedy and The Qt Company's entire liability for deficiencies or errors in the Services shall be limited, at The Qt Company's option, to correction of the error, replacement of the Services, re-performance of the Service or return of the applicable fees paid for the defective Service for the time period during which Customer was not able to utilize the Service as agreed. + +9. LIMITATION OF LIABILITY +9.1. EXCEPT FOR (I) CASES OF GROSS NEGLIGENCE OR INTENTIONAL MISCONDUCT, (II) A BREACH OR VIOLATION OF THE OTHER PARTY'S INTELLECTUAL PROPERTY RIGHTS, OR (III) WHERE REQUIRED BY APPLICABLE LAW, IN NO EVENT SHALL EITHER PARTY BE LIABLE TO THE OTHER PARTY FOR ANY LOST PROFITS, LOSS OF DATA, LOSS OF BUSINESS OR GOODWILL OR ANY OTHER INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE COST, DAMAGES OR EXPENSE OF ANY KIND, HOWSOEVER ARISING UNDER OR IN CONNECTION WITH THIS AGREEMENT. +9.2. EXCEPT FOR (I) CASES OF GROSS NEGLIGENCE OR INTENTIONAL MISCONDUCT, (II) A BREACH OR VIOLATION OF THE OTHER PARTY'S INTELLECTUAL PROPERTY RIGHTS, AND TO THE EXTENT PERMITTED BY APPLICABLE LAW, IN NO EVENT SHALL EITHER PARTY'S TOTAL AGGREGATE LIABILITY UNDER THIS AGREEMENT EXCEED THE AGGREGATE FEES PAID OR PAYABLE TO THE QT COMPANY BY CUSTOMER FOR THE RESPECTIVE LICENSED SOFTWARE OR SERVICE GIVING RISE TO THE LIABILITY. THE FOREGOING LIMITATION WILL NOT APPLY TO CUSTOMER'S OBLIGATION TO PAY THE APPLICABLE FEES CORRESPONDING TO ITS ACTUAL USE OF LICENSED SOFTWARE OR SERVICES. + +10. TERM AND TERMINATION +10.1. This Agreement shall enter into force upon signing by both Parties and is effective as of the last date of signature. +10.2. This Agreement shall remain in force until further notice and may be terminated without cause by either Party by no less than three (3) months' prior written notice to the other Party. +10.3. Termination of a particular Purchase Document and the Services governed thereunder shall be stipulated under the applicable Service Terms. +10.4. Either Party may terminate this Agreement with immediate effect, if the other Party: +(i) commits a material breach of the terms of this Agreement (including applicable Service Terms) and has not remedied such breach within a reasonable period of time (which shall be no less than thirty (30) days) of the non-breaching Party's written notice specifying the breach, or +(ii) becomes bankrupt, insolvent or goes into liquidation or debt restructuring. +10.5. Termination of this Agreement shall, as such, have no effect on the validity of any Services ordered and agreed prior to the effective date of such termination, and such Services shall continue to remain in force pursuant to applicable Service Terms (including the terms of this Agreement) for the remainder of the duration of the applicable Service validity term. + +11. GOVERNING LAW AND DISPUTE RESOLUTION +11.1. The United Nations Convention on Contracts for the International Sale of Goods will not apply to this Agreement. +11.2. Where this Agreement is concluded with The Qt Company, Inc., a Delaware corporation, the Parties agree that this Section 10.2 will apply. This Agreement will be governed by, and construed in accordance with the laws of the State of California and any controlling United States federal law. Any dispute, controversy or claim arising out of or relating to this contract, including the formation, interpretation, breach or termination thereof, and whether the claims asserted are arbitrable, will be referred to and finally determined by arbitration in accordance with the JAMS International Arbitration Rules. The tribunal will consist of one arbitrator. The place of arbitration will be San Francisco, California, USA. The language to be used in the arbitral proceedings will be English. Judgment upon the award rendered by the arbitrator(s) may be entered in any court having jurisdiction thereof. This Section 10.2 shall not preclude parties from seeking provisional remedies in aid of arbitration from a court of appropriate jurisdiction. Notwithstanding the foregoing, any action by The Qt Company solely to collect license or other fees hereunder may be brought in any court of competent jurisdiction. +11.3. Where this Agreement is concluded with The Qt Company, Oy., a Finnish company, the parties agree that this Section 10.3 will apply. This Agreement shall be construed and interpreted in accordance with the laws of Finland, excluding its choice of law provisions. All disputes arising out of or in connection with this Agreement shall be finally settled in accordance with the laws of Finland, excluding its choice of law provisions. All disputes arising out of or in connection with this Agreement shall be finally settled under the Rules of Arbitration of the International Chamber of Commerce by one or more arbitrators appointed in accordance with the said Rules. The place of arbitration will be Helsinki, Finland. The language to be used in arbitral proceedings will be English. This Section 10.3 shall not preclude parties from seeking provisional remedies in aid of arbitration from a court of appropriate jurisdiction. + +12. MISCELLANOUS +12.1. No Assignment. Customer shall not be entitled to assign or transfer all or any of its rights, benefits and obligations under this Agreement except in case of sale of relevant business or assets or otherwise with prior written consent of The Qt Company, which shall not be unreasonably withheld or delayed. The Qt Company shall be entitled to freely assign or transfer any of its rights, benefits or obligations under this Agreement. +12.2. Surviving Sections. Any terms and conditions that by their nature or otherwise reasonably should survive termination of this Agreement shall so be deemed to survive. +12.3. Entire Agreement. This Agreement, its Appendices and any applicable Purchase Documents constitute the complete agreement between the Parties and supersedes all prior or contemporaneous discussions, representations, contracts (including prior License Agreements and similar prior agreements), and proposals, written or oral, with respect to the subject matters discussed herein. +12.4. Subcontractors. The Qt Company may utilize subcontractors in the performance of Services under this Agreement, provided that The Qt Company remains responsible for the performance of the Services and compliance with this Agreement, as well as ensuring that subcontractors are required to abide by relevant restrictions (e.g., confidentiality) set forth in this Agreement. +12.5. Modifications. No modification of this Agreement shall be effective unless contained in a writing executed by an authorized representative of each Party. No standard terms and conditions or provisions of any Customer purchase order or other ordering form that Customer may use in connection with the acquisition of Services will modify or affect this Agreement, the parties agree that any such terms and conditions are void with no legal effect. +12.6. Affiliate Orders. Customer Affiliates may purchase Services via this Agreement as follows: +(i) any purchases by Customer Affiliates from The Qt Company or its Affiliates will create a contractual relationship directly between the relevant The Qt Company entity and the respective ordering Customer Affiliate; +(ii) the entry into a Purchase Document between The Qt Company and Customer Affiliate creates an agreement between The Qt Company and Customer Affiliate and incorporates all terms and conditions of this Agreement as the governing agreement between The Qt Company and Customer Affiliate ("Accession Agreement"): and +(iii) Customer Affiliate will be deemed "Customer" under the terms of this Agreement and all rights and obligations under such Accession Agreement are vested and borne solely by the ordering Customer Affiliate and the relevant The Qt Company entity as contracting parties under such Accession Agreement. +12.7. Force Majeure. Neither Party shall be liable to the other for any delay or non-performance of its obligations hereunder in the event and to the extent that such delay or non-performance is due to an event of act of God, terrorist attack or other similar unforeseeable catastrophic event that prevents either Party for fulfilling its obligations under this Agreement and which such Party cannot avoid or circumvent ("Force Majeure Event"). If the Force Majeure Event results in a delay or non-performance of a Party for a period of three (3) months or longer, then either Party shall have the right to terminate the relevant Purchase Document and Services thereunder with immediate effect without any liability (except for the obligations of payment arising prior to the Force Majeure Event) towards the other Party. +12.8. Notices. Any notice given by one Party to the other shall be deemed properly given and deemed received if specifically acknowledged by the receiving Party in writing or when successfully delivered to the recipient by hand, fax, or special courier during normal business hours on a business day to the addresses specified for each Party in this Agreement. Each communication and document made or delivered by one Party to the other Party pursuant to this Agreement shall be in the English language. +12.9. Attorney Fees. The prevailing Party in any action to enforce this Agreement shall be entitled to recover its attorney's fees and costs in connection with such action. +12.10. Privacy and Security. The Parties commit to and comply with their respective applicable obligations under the privacy and security terms set forth in the Privacy and Security Appendix and relevant Appendices attached hereto. +12.11. Feedback. Customer agrees that, from time to time, The Qt Company, may request feedback from Customer regarding the Services ("Feedback"). Customer may choose to provide Feedback and agrees that The Qt Company may freely use, copy, disclose, and exploit any Feedback. No Feedback will be considered Customer Confidential Information unless explicitly agreed otherwise between the Parties. +12.12. Export Control. Customer acknowledges that the Services, or portions thereof, may be subject to export control restrictions under the applicable laws of respective countries. Customer shall fully comply with all applicable export license restrictions and requirements, economic sanctions restrictions, as well as with all laws and regulations relating thereto, and shall procure all necessary governmental authorizations, including without limitation, all necessary licenses, approvals, permissions, or consents, where necessary (e.g., for re-exportation of the Redistributables, Applications and/or Devices, each as defined in the relevant Service Terms). +12.13. Severability. If any provision of this Agreement shall be adjudged by any court of competent jurisdiction to be unenforceable or invalid, that provision shall be limited or eliminated to the minimum extent necessary so that this Agreement shall otherwise remain in full force and effect and enforceable. + +13. APPENDICES +13.1. The following appendices form an integral part of this Agreement. In case of a discrepancy between this Agreement and any of its Appendices, this Agreement shall prevail. In case of discrepancies between the Purchase Document(s) and this Agreement or applicable Service Terms, the terms of this Agreement or the applicable Service Terms shall prevail, except in cases where an express deliberate deviation from the terms of this Agreement or applicable Service Terms has been concluded pursuant to Section 2.3 hereof, in which case the Purchase Document shall prevail. +1. Appendix for Qt Development Framework +2. Appendix for Support Terms https://www.qt.io/terms-conditions/support-terms +3. Appendix for Privacy and Security Terms https://www.qt.io/terms-conditions/privacy-and-security + +Appendix for Qt Development Framework +Version 2024-02 + +1. This Appendix for Qt Development Framework is an integral part of the Agreement and specifies the legal terms for the licensing of Licensed Software (as defined below) between The Qt Company and the Customer. Entry into this Appendix governs the use of and supersedes any prior contracts between the Parties (including prior License Agreements and similar prior agreements), with respect to the Licensed Software under this Appendix. + +2. DEFINITIONS +2.1. Capitalized words used in this Appendix shall have the meanings described in the Agreement or as defined below. +2.2. "Add-on Products" shall mean The Qt Company's specific add-on software products which are not licensed as part of The Qt Company's standard Services offerings, but shall be included into the scope of Licensed Software only if so specifically agreed between the Parties. +2.3. "Application" means software products created using the Licensed Software, which include the Redistributables, or part thereof. +2.4. "End Customer" shall mean Customer's customer(s) to whom Customer, directly or indirectly, distributes copies of the Redistributables as integrated or incorporated into Applications or Devices. +2.5. "Data Protection Legislation" shall mean the General Data Protection Regulation (EU 2016/679) (GDPR) and any national implementing laws, regulations and secondary legislation, as may be amended or updated from time to time, as well as any other data protection laws or regulations applicable in the relevant territory. +2.6. "Deployment Platforms" shall mean target operating systems and/or hardware specified in the License Certificate, on which the Redistributables can be distributed pursuant to the terms and conditions of this Appendix. +2.7. "Designated User(s)" shall mean the employee(s) of Customer or Customer's Affiliates acting within the scope of their employment or Customer's Contractors acting within the scope of their services on behalf of Customer. +2.8. "Development License" shall mean the license needed by the Customer for each Designated User to use Licensed Software under the license grant described in Section 5 of this Appendix. Development Licenses are available per respective Licensed Software products; each product having its designated scope and purpose of use. +2.9. "Development Platforms" shall mean the host operating system(s) specified in the License Certificate, on which Licensed Software can be used under the Development License. +2.10. "Devices" shall mean +(i) hardware devices or products that +a. are manufactured and/or distributed by the Customer, its Affiliates, Contractors or End Customer, and +b. incorporate, integrate or link to Applications such that substantial functionality of such unit, when used by an End User, is provided by Application(s) or otherwise depends on the Licensed Software; or +(ii) Applications designed for the hardware devices specified in item (i). +Devices covered by this Appendix shall be specified in the Pricing Appendix or Purchase Document. +2.11. "Distribution License(s)" shall mean a royalty-bearing license required for any kind of sale, trade, exchange, loan, lease, rental or other distribution by or on behalf of Customer to a third party of Redistributables in connection with Devices pursuant to license grant described in Section 5.3 of this Appendix. Distribution Licenses are sold separately for each type of Device respectively and cannot be used for any other type of Devices. +2.12. "Distribution License Packs" shall mean set of prepaid Distribution Licenses for distribution of Redistributables, as defined in The Qt Company's standard price list, quote, Pricing Appendix or in the Purchase Document, as applicable. +2.13. "Evaluation License Term" shall mean a time period specified in the License Certificate for the Customer to use the relevant Licensed Software for evaluation purposes according to Section 5.6 of this Appendix. +2.14. "Intellectual Property Rights" shall mean patents (including utility models), design patents, and designs (whether or not capable of registration), chip topography rights and other like protection, copyrights, trademarks, service marks, trade names, logos or other words or symbols and any other form of statutory protection of any kind and applications for any of the foregoing as well as any trade secrets. +2.15. "License Certificate" shall mean a certificate generated by The Qt Company for each Designated User respectively upon their download of the Licensed Software, which will be available under the respective Designated User's Qt Account at account.qt.io. License Certificates will specify relevant information pertaining to the Licensed Software purchased by Customer and the license to the Licensed Software. +2.16. "License Fee" shall mean the fee charged to Customer for rights granted under this Appendix. +2.17. "Licensed Software" shall mean the specified product(s) of Qt Software which Customer has purchased and which is provided to Customer under the terms of this Appendix (including its Exhibits). Licensed Software shall include corresponding online or electronic documentation, associated media and printed materials, including source code (where applicable), example programs and the documentation. Licensed Software does not include Third Party Software (as defined in Section 6) or Qt Community Edition. The Qt Company may, in the course of its development activities, at its free and absolute discretion and without any obligation to send or publish any notifications to Customer or in general, make changes, additions or deletions in the components and functionalities of the Licensed Software, provided that no such changes, additions or deletions will affect the already released version of the Licensed Software, but only upcoming version(s). +2.18. "License Term" shall mean the agreed validity period of the Development License during which the relevant Licensed Software product can be used pursuant to this Appendix. The agreed License Term, as ordered and paid for by Customer, shall be memorialized in the applicable License Certificate. +2.19. "Customer's Records" shall mean books and records that contain information bearing on Customer's compliance with the Agreement, Customer's use of Qt Community Edition and/or the payments due to The Qt Company under the Agreement, including, but not limited to user information, assembly logs, sales records and distribution records. +2.20. "Modified Software" shall have the meaning as set forth below in Section 4. +2.21. "Qt Software" shall mean the development and design software of The Qt Company, which The Qt Company makes available under commercial and/or open source licenses as either the "Licensed Software" or the "Qt Community Edition". +2.22. "Permitted Software" shall mean third party products that are generally available to the public, which may include parts of Qt Community Edition or be developed using Qt Community Edition. +2.23. "Pre-Release Code" shall have the meaning as set forth in Section 7. +2.24. "Prohibited Combination" shall mean any effort to use, combine, incorporate, link or integrate Licensed Software with any software created with or incorporating Qt Community Edition, or use Licensed Software for creation of any such software. +2.25. "Qt Community Edition" shall mean the open source version of Qt Software available under the terms of the GNU Lesser General Public License, version 2.1 or later ("LGPL") or the GNU General Public License, version 2.0 or later ("GPL"). For clarity, Qt Community Edition shall not be provided, governed or used under this Appendix. +2.26. "Redistributables" shall mean the portions of Licensed Software as set forth in Exhibit 1 hereto that may be distributed pursuant to this Appendix in object code form only, including any relevant documentation. Where relevant, any reference to Licensed Software in this Appendix includes and refers to Redistributables. +2.27. "Renewal Term" shall mean an extension of the previous License Term as agreed between the Parties. +2.28. "Submitted Modified Software" shall have the meaning as set forth in Section 4.2 of this Appendix. +2.29. "Third-Party Software" shall have the meaning set forth in Section 6 of this Appendix. +2.30. "Updates" shall mean a release or version of the Licensed Software containing bug fixes, error corrections and other changes that are generally made available to users of the Licensed Software that have contracted for Support. Updates are generally depicted as a change to the digits following the decimal in the Licensed Software version number. The Qt Company shall make Updates available to Customer under the Support. Updates shall be considered as part of the Licensed Software hereunder. +2.31. "Upgrades" shall mean a release or version of the Licensed Software containing enhancements and new features and are generally depicted as a change to the first digit of the Licensed Software version number. In the event that Upgrades are provided to Customer under this Appendix, they shall be considered as part of the Licensed Software hereunder. + +3. OWNERSHIP +3.1. Ownership of The Qt Company +3.1.1. The Licensed Software is protected by copyright laws and international copyright treaties, as well as other intellectual property laws and treaties. The Licensed Software is licensed, not sold. +3.1.2. All of The Qt Company's Intellectual Property Rights are and shall remain the exclusive property of The Qt Company or its respective licensors . No rights to The Qt Company's Intellectual Property Rights are assigned or granted to Customer under this Appendix, except when and to the extent expressly specified herein. +3.2. Ownership of Customer +3.2.1. All of Customer's Intellectual Property Rights are and shall remain the exclusive property of Customer or its licensors respectively. +3.2.2. Except to the extent set forth in this Appendix, all Intellectual Property Rights to the Modified Software, Applications and Devices (except to Redistributables included therein) shall remain with Customer. + +4. MODIFIED SOFTWARE +4.1. Customer may create bug-fixes, error corrections, patches or modifications to the Licensed Software ("Modified Software"). To the extent that Customer's Modified Software breaks source or binary compatibility or other functionality with the Licensed Software, Customer acknowledges that The Qt Company's ability to provide Support may be prevented or limited and Customer's ability to make use of Updates may be restricted. +4.2. Customer may, at its sole and absolute discretion, choose to submit Modified Software to The Qt Company ("Submitted Modified Software") in connection with Customer's Support request, service request or otherwise. In the event Customer does so, then, Customer hereby grants The Qt Company a sublicensable, assignable, irrevocable, perpetual, worldwide, non-exclusive, royalty-free and fully paid-up license, under all of Customer's Intellectual Property Rights, to reproduce, adapt, translate, modify, and prepare derivative works of, publicly display, publicly perform, sublicense, make available and distribute such Submitted Modified Software as The Qt Company sees fit at its free and absolute discretion. + +5. LICENSES GRANTED +5.1. Development with Licensed Software +5.1.1. Subject to the terms of the Agreement, The Qt Company grants to Customer a worldwide, non-exclusive, non-transferable license, valid for each License Term, to use, modify and copy the Licensed Software by Designated Users on the Development Platforms for the sole purposes of designing, developing, demonstrating and testing Application(s) and/or Devices, and to provide support and other services related to such Applications and Devices to End Customers. Each Application and/or Device can only include, incorporate or integrate contributions by such Designated Users who are duly licensed for the applicable Development Platform(s) and Deployment Platform(s) (i.e have a valid license for the appropriate Licensed Software product and only use one type of Qt Development License per Customer Application and/or Device(s)). +5.1.2. Customer may install copies of the Licensed Software on five (5) computers per Designated User, provided that only Designated Users who have a valid Development License may use the Licensed Software. +5.1.3. Customer may designate another Designated User to replace a then-current Designated User by notifying The Qt Company in writing, where such replacement is due to termination of employment, long-term absence or other permanent reason affecting Designated User's need for Licensed Software. +5.1.4. Upon expiry of the initially agreed License Term, the respective License Term shall be automatically extended by one or more Renewal Term(s), unless and until either Party notifies the other Party in writing, that it does not wish to continue the License Term, such notification to be provided to the other Party no less than thirty (30) days before expiry of the respective License Term. The Qt Company shall, in good time before the due date for the above notification, remind the Customer on the coming Renewal Term. Unless otherwise agreed between the Parties, Renewal Term shall be equal to the length of the previous License Term, but no longer than thirty-six (36) months. +5.1.5. Any such Renewal Term shall be subject to License Fees agreed between the Parties or, if no advance agreement exists, subject to The Qt Company's standard list pricing applicable at the commencement date of any such Renewal Term. +5.2. Distribution of Applications +5.2.1. Subject to the terms of the Agreement, The Qt Company grants to Customer a worldwide, non-exclusive, non-transferable, perpetual, royalty-free and revocable (only for Customer’s material breach of agreement) right and license to: +(i) distribute, by itself or through its Contractors, Redistributables as installed, incorporated or integrated into Applications for execution on the Deployment Platforms; and +(ii) grant perpetual and irrevocable sublicenses to Redistributables, as distributed hereunder, for End Customers solely to the extent necessary in order for the End Customers to use the Applications for their respective intended purposes. +5.2.2. Right to distribute the Redistributables as part of an Application as provided herein is not royalty-bearing but is conditional upon the Application having been created, updated and maintained under a valid and duly paid Development License. +5.3. Distribution of Devices +5.3.1. Subject to the terms of the Agreement, The Qt Company grants to Customer a worldwide, non-exclusive, non-transferable, perpetual, revocable (only for Customer’s material breach of agreement), royalty-bearing right and license to: +(i) distribute, by itself or through one or more tiers of Contractors, Redistributables as installed, incorporated or integrated, or intended to be installed, incorporated or integrated into Devices for execution on the Deployment Platforms; and +(ii) grant perpetual and irrevocable sublicenses to Redistributables, as distributed hereunder, for End Customers solely to the extent necessary in order for the End Customers to use the Devices for their respective intended purposes. +5.3.2. Right to distribute the Devices as provided herein is conditional upon (i) the Devices having been created, updated and maintained under a valid and duly paid Development License, and (ii) Customer having acquired corresponding Distribution Licenses at the time of distribution of any Devices to End Customers. +5.4. Further Requirements +5.4.1. The licenses granted in this Section 5 by The Qt Company to Customer are conditional and subject to Customer's compliance with the following terms: +(i) Customer acknowledges that The Qt Company has separate products for the purpose of Applications and Devices respectively, where development and distribution of Devices is only allowed using the correct designated product. Customer shall ensure and bear the burden of proof that Customer is using a correct product entitling Customer to development and distribution of Devices; +(ii) Customer shall not remove or alter any copyright, trademark or other proprietary rights notice(s) contained in any portion of the Licensed Software; +(iii) Applications must add primary and substantial functionality to Licensed Software so as not to compete with the Licensed Software; +(iv) Applications may not pass on functionality which in any way makes it possible for others to create software with Licensed Software; provided however that Customer may use Licensed Software’s scripting and QML ("Qt Quick") functionality solely in order to enable scripting, themes and styles that augment the functionality and appearance of the Application(s) without adding primary and substantial functionality to the Application(s); +(v) Customer shall not use Licensed Software in any manner or for any purpose that infringes, misappropriates or otherwise violates any Intellectual Property Right or right of any third party, or that violates any applicable law; +(vi) Customer shall not use The Qt Company's or any of its suppliers' names, logos, or trademarks to market Applications, except that Customer may use “Built with Qt” logo to indicate that an Application or Device was developed using Licensed Software; +(vii) Customer shall not distribute, sublicense or disclose source code of Licensed Software to any third party (provided however that Customer may appoint employee(s) of Contractors and Affiliates as Designated Users to use Licensed Software pursuant to this Appendix). +(viii) Customer shall not grant the End Customers a right to: (a) make copies of the Redistributables except when and to the extent required to use the Applications and/or Devices for their intended purpose; (b) modify the Redistributables or create derivative works thereof; (c) decompile, disassemble or otherwise reverse engineer Redistributables; or (d) redistribute any copy or portion of the Redistributables to any third party, except as part of the onward sale of the Application or Device on which the Redistributables are installed; +(ix) Customer shall not, and shall cause that its Affiliates or Contractors shall not, use Licensed Software in any Prohibited Combination, unless Customer has received specific advance written permission from The Qt Company to do so. Absent such written permission, any and all distribution by Customer during the term of the Agreement of a hardware device or product: a) which incorporates or integrates any part of Licensed Software or Qt Community Edition; or b) where substantial functionality is provided by software built with Licensed Software or Qt Community Edition or otherwise depends on Licensed Software or Qt Community Edition, shall be considered to be Device distribution under this Appendix and shall be dependent on Customer’s compliance thereof (including but not limited to the obligation to pay applicable License Fees for such distribution). Notwithstanding the foregoing, Customer is entitled to use and combine Licensed Software with Permitted Software; +(x) Customer shall cause all of its Affiliates, Contractors and End Customer entitled to make use of the licenses granted under this Appendix, to be contractually bound to comply with the relevant terms hereof and not to use the Licensed Software beyond the terms hereof nor for any purposes other than operating within the scope of their services for Customer. Customer shall be responsible for any and all actions and omissions of its Affiliates, Contractors, and End Customers relating to the Licensed Software and use thereof (including but not limited to payment of all applicable License Fees); +(xi) Except when and to the extent explicitly provided in this Section 5, Customer shall not transfer, publish, disclose, display or otherwise make available the Licensed Software; and +(xii) Customer shall not attempt or enlist a third party to conduct or attempt to conduct any of the above. +5.4.2. The above terms shall not be applicable if and solely to the extent they conflict with any mandatory provisions of applicable laws. +5.4.3. Any use of Licensed Software beyond the provisions of this Appendix is strictly prohibited and requires, at a minimum an additional license from The Qt Company (e.g. certain additional rights granted under software development kit “SDK” agreement with regard to limitations of Section 5.4.1 iv, vii or viii). +5.5. Evaluation License +5.5.1. Subject to the terms of this Appendix, The Qt Company grants to Customer a worldwide, non-exclusive, non-transferable license, valid for the Evaluation License Term to use the relevant Licensed Software product solely for Customer’s internal use to evaluate and determine whether the Licensed Software meets Customer's business requirements, specifically excluding any commercial use of the Licensed Software or any derived work thereof. +5.5.2. Upon the expiry of the Evaluation License Term, Customer must either discontinue use of the relevant Licensed Software or acquire a commercial Development License specified herein. + +6. THIRD-PARTY SOFTWARE. The Licensed Software may provide links or access to third party libraries or code (collectively "Third-Party Software") to implement various functions. Third-Party Software does not, however, comprise part of the Licensed Software, but is provided to Customer complimentary and use thereof is discretionary for Customer. Third-Party Software will be listed in the ".../src/3rdparty" source tree delivered with the Licensed Software or documented in the Licensed Software, as such may be amended from time to time. Customer acknowledges that use or distribution of Third-Party Software is in all respects subject to applicable license terms of applicable third-party right holders. + +7. PRE-RELEASE CODE +7.1. The Licensed Software may contain pre-release code and functionality, or sample code marked or otherwise stated with appropriate designation such as "Technology Preview", "Alpha", "Beta", "Experimental", "Sample", "Example" etc. ("Pre-Release Code"). +7.2. Such Pre-Release Code may be provided complimentary for Customer, in order to provide experimental support or information for new platforms or preliminary versions of one or more new functionalities, or for other similar reasons. Pre-Release Code may not be at the level of performance and compatibility of a final, generally available, product offering. Pre-Release Code may not operate correctly, may contain errors and may be substantially modified by The Qt Company prior to a commercial product release, if any. The Qt Company is under no obligation to make Pre-Release Code commercially available, or provide any Support or Updates relating thereto. To the maximum extent permitted by law, the Qt Company assumes no liability whatsoever regarding any Pre-Release Code and any use thereof is exclusively at Customer's own risk and expense. +7.3. Unless Licensed Software specifies different license terms for the respective Pre-Release Code, Customer is entitled to use such pre-release code pursuant to Section 5 of this Appendix, just like other Licensed Software. + +8. SUPPORT. Support is provided according to agreed support level and subject to applicable requirements and restrictions, as specified in the Appendix for Support Terms. + +9. FEES AND ORDERING: DISTRIBUTION LICENSES +9.1. Distribution License Packs +9.1.1. Unless otherwise agreed in writing, Distribution Licenses shall be purchased by way of Distribution License Packs. +9.1.2. Upon due payment of the ordered Distribution License Pack(s), Customer will have an account of Distribution Licenses available for distributing the Redistributables in accordance with this Agreement. +9.2. Each time Customer distributes a copy of Redistributables, one Distribution License is used and Customer's account of available Distribution Licenses is decreased accordingly. +9.3. Customer may distribute copies of the Redistributables so long as Customer has Distribution Licenses remaining on its account. + +10. RECORD-KEEPING AND REPORTING OBLIGATIONS; AUDIT RIGHTS +10.1. Customer's Record-keeping +10.1.1. Customer shall at all times during the term of the Agreement or validity of any of the licenses hereunder, whichever is later, and for a period of two (2) years thereafter, maintain Customer's Records in an accurate and up-to-date form. Customer's Records shall be adequate to reasonably enable The Qt Company to determine Customer's compliance with the provisions of the Agreement. The records shall conform to general good accounting practices. +10.1.2. Customer shall, within thirty (30) days from receiving The Qt Company's request to that effect, deliver to The Qt Company a report based on Customer's Records, such report to contain information, in sufficient detail, on: (i) number and identity of users working with Licensed Software or Qt Community Edition, (ii) copies of Redistributables distributed by Customer during the most recent calendar quarter and/or any other term specified by The Qt Company, and (iii) any other information pertaining to Customer's compliance with the terms of the Agreement (e.g. information on products and/or projects relating to use of Distribution Licenses), as The Qt Company may reasonably require from time to time. +10.2. The Qt Company's Audit Rights +10.2.1. The Qt Company or an independent auditor acting on behalf of The Qt Company may, upon at least thirty (30) days' prior written notice and at The Qt Company expense, audit Customer with respect to Customer's use of the Licensed Software, but not more frequently than once during each six (6) month period. Such audit may be conducted by mail, electronic means or through an in-person visit to Customer's place of business. Any possible in-person audit shall be conducted during regular business hours at Customer's facilities, shall not unreasonably interfere with Customer's business activities and shall be limited in scope to verify Customer's compliance with the terms of the Agreement. The Qt Company or its independent auditor shall be entitled to inspect Customer's Records and conduct necessary interviews of Customer's relevant employees and Contractors. All Customer's Records and use thereof shall be subject to the obligation of confidentiality under the Agreement. +10.2.2. If an audit reveals that Customer is using the Licensed Software beyond scope of the licenses Customer has paid for, Customer shall pay to The Qt Company any amounts owed for such unauthorized use within thirty (30) days from receipt of the corresponding invoice from The Qt Company. +10.2.3. In addition, in the event the audit reveals a material violation of the terms of the Agreement (without limitation, either (i) underpayment of more than 10% of License Fees or 10,000 euros (whichever is more) or (ii) distribution of products, which include or result from Prohibited Combination, shall be deemed a material violation for purposes of this section), then Customer shall pay The Qt Company's reasonable cost of conducting such audit. + +11. TERMINATION +11.1. Termination of Licenses +11.1.1. The Qt Company may terminate Customer's rights to any and all Licensed Software (including access to Support), if Customer: +(i) commits a material breach of the Agreement (including this Appendix) and has not remedied the breach within a reasonable period of time (which shall be no less than 30 days) of The Qt Company's written notice specifying the breach, or +(ii) becomes bankrupt, insolvent or goes into liquidation or debt restructuring. +11.2. Suspension of rights: Instead of termination, The Qt Company reserves the right to suspend or withhold grants of any and all rights to the Licensed Software (including Support), should Customer fail to make payment in timely fashion or otherwise violate or is reasonably suspected of violating its obligations under the Agreement and/or this Appendix, and where such violation or breach is not cured within ten (10) business days following The Qt Company's written notice thereof. +11.3. Parties Rights and Duties upon Termination +11.3.1. Upon expiry or termination of the Development Licenses, Customer shall cease and shall cause all Designated Users (including those of its Affiliates' and Contractors') to cease using the relevant Licensed Software. +11.3.2. Upon such expiry or termination of Development Licenses, Customer shall destroy or return to The Qt Company all copies of the respective Licensed Software and all related materials and will certify the same by Customer's duly authorized officer to The Qt Company upon its request, provided however that Customer may retain and utilize such copies of the Licensed Software to the extent required to provide Customer's continued support to End Customers, for archiving purposes or as is required under applicable law. +11.3.3. Distribution Licenses are perpetual and, therefore, Customer's distribution rights hereunder shall only terminate upon The Qt Company's termination of Distribution Licenses due to Customer's material breach as set forth in Section 11.1.1(i) of this Appendix. In case of such termination by The Qt Company due to Customer's material breach, Customer must cease any distribution of Applications and Devices at the effective date of termination. +11.3.4. Expiry or termination of any of Customer's licenses hereunder for any reason whatsoever shall not: +(i) relieve Customer of its obligation to pay any License Fees accrued or payable to The Qt Company prior to the effective date of termination, and Customer pay to The Qt Company all such fees within 30 days from the effective date of termination of the licenses; +(ii) relieve Customer of its obligation to ensure that Applications and Devices (including those already distributed) remain in compliance with the terms of the Agreement; nor +(iii) affect any rights of End Customer to continue use of Applications and Devices (and therein incorporated Redistributables). +11.4. Extension of Rights under Special Circumstances. In the event that, during the applicable License Term, The Qt Company is declared bankrupt under a final, non-cancellable decision by relevant court of law, and the Agreement is not, at the date of expiry of the Development License(s), assigned to a party who has assumed The Qt Company's position as a legitimate licensor of Licensed Software under the Agreement, then all valid Development Licenses possessed by Customer at such date of expiry, and which Customer has not notified for expiry, shall be extended to be valid in perpetuity under the terms of the Agreement. Any such extension shall not apply to The Qt Company's Support obligations. + +EXHIBIT 1, Licensed Software +At the time of conclusion of this Appendix, the latest available version of Licensed Software includes the software libraries and tools set forth in Exhibit 1 (as provided below), depending on which product(s) Customer has purchased under the relevant Purchase Document. +The modules and tools are specific to each product version respectively and may vary from version to version. Modules and tools included in the latest publicly available version of the respective product at any given time are listed in Exhibit 1 of https://www.qt.io/terms-conditions/qt-dev-framework/exhibit-1. If a new version of Licensed Software does not include a module or tool present in an older version which Customer is entitled to use under a valid license from The Qt Company, then Customer will continue to have such right during the validity of Customer's license to relevant Licensed Software. In the event a new version of the Licensed Software adds modules or tools to any previous version(s), Customer's rights will extend to cover also such additional modules and tools. + +EXHIBIT 2 - Small Business Terms +1. This Exhibit applies to entities that qualify as a Qualified Small Business (defined below) and provides additional terms and conditions applicable to small business pricing and licensing. In the event that Customer is a Qualified Small Business and there is any conflict between the terms of this Exhibit and any other terms of the Agreement, the terms in this Exhibit shall take precedence. + +2. APPLICABILITY FOR SMALL BUSINESS LICENSES. Any small business discounts applied require that Customer (including any Customer Affiliates or group entities) has an annual revenue (including annual capital funding) below 1 Million EUR, or the equivalent thereof, as approved by The Qt Company (each, a "Qualified Small Business"). The annual revenue, including funding, must be evidenced upon request by business records and approved by The Qt Company in its reasonable discretion. + +3. SUPPORT. Support is limited to: (i) Install Support; and (ii) for any other Standard Support issue, five (5) support tickets annually. + +4. LIMITATION ON NUMBER OF SMALL BUSINESS DEVELOPER LICENSES. Qualified Small Business discounts and purchasing structure may be applied to a maximum of three discounted developer licenses (either ADE or DCP) per Qualified Small Business. Any additional licenses purchased will be at The Qt Company list price in effect at the time. + +5. LIMITATION FOR NUMBER OF INSTALLATIONS. Customer may install copies of the Licensed Software on two (2) computers per Designated User, provided that only the Designated Users who have a valid Development License may use the Licensed Software. + +6. CONDITIONAL WAIVER OF DISTRIBUTION LICENSES. For Qualified Small Businesses, the Agreement requirements to purchase Distribution Licenses for Devices shall apply only when Customer ceases to be a Qualified Small Business (e.g., when annual revenue threshholds are bypassed). + +7. ADDITIONAL TERMS FOR RENEWALS. The initial subscription purchase term for Qualified Small Business Licenses is twelve (12) months. Upon expiration of the initial twelve (12) month term and unless terminated in accordance with the Agreement, the Licenses will automatically renew for additional twelve (12) month terms with applicable Qualified Small Business discounts. If Customer ceases to be a Qualified Small Business, renewal pricing shall be at The Qt Company list price in effect at the time of renewal, or as agreed in writing between the parties. + +8. ADDITIONAL AUDIT RIGHTS. In addition to the audit rights set forth in the Agreement, The Qt Company reserves the right to audit Customer financial records in order to determine whether Customer is a Qualified Small Business. diff --git a/.flake8 b/.flake8 new file mode 100644 index 00000000..ef866035 --- /dev/null +++ b/.flake8 @@ -0,0 +1,9 @@ +[flake8] +ignore = E115,E265,W503 +max-line-length = 100 +exclude = rc_*.py,*_rc.py,ui_*.py +per-file-ignores = + # for init_test_paths() hack + *_test_*.py:E402 + __init__.py:F401,E402 + diff --git a/.vscode/settings.json b/.vscode/settings.json index 57f2286d..3d19a40b 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -12,5 +12,8 @@ "*_test.py" ], "python.testing.pytestEnabled": false, - "python.testing.unittestEnabled": true + "python.testing.unittestEnabled": true, + "flake8.args": [ + "--config=.flake8" + ] } diff --git a/README.md b/README.md index 449a40ba..cdeb0398 100644 --- a/README.md +++ b/README.md @@ -113,6 +113,8 @@ using `setup.py build`: or macOS only). * `--verbose-build`, will output the compiler invocation with command line arguments, etc. + * `--disable-pyi`, will suppress the generation of .pyi files. This allows + debugging when the project builds but the pyi generator complains. ## Requirements diff --git a/README.pyside6_addons.md b/README.pyside6_addons.md index 0ed65d25..3247a550 100644 --- a/README.pyside6_addons.md +++ b/README.pyside6_addons.md @@ -22,6 +22,7 @@ it includes the following Qt modules: * QtBluetooth * QtCharts * QtDataVisualization +* QtGraphs * QtMultimedia * QtMultimediaWidgets * QtNetworkAuth @@ -32,7 +33,10 @@ it includes the following Qt modules: * QtScxml * QtSensors * QtSerialPort +* QtSerialBus +* QtSpatialAudio * QtStateMachine +* QtTextToSpeech * QtVirtualKeyboard * QtWebChannel * QtWebEngineCore @@ -42,6 +46,8 @@ it includes the following Qt modules: * QtPdf * QtPdfWidgets * QtHttpServer +* QtLocation +* QtAsyncio ### Documentation and Bugs diff --git a/build_history/blacklist.txt b/build_history/blacklist.txt index 60cc3f95..5f0183f5 100644 --- a/build_history/blacklist.txt +++ b/build_history/blacklist.txt @@ -45,8 +45,24 @@ win32 [QtQml::bug_825] py3.8 # bug in typeobject::type_mro_modified, fix in 3.9 - py3.9 # fixed in 3.9.12 - py3.10 # fixed in 3.10.4 + py3.9.0 + py3.9.1 + py3.9.2 + py3.9.3 + py3.9.4 + py3.9.5 + py3.9.6 + py3.9.7 + py3.9.8 + py3.9.9 + py3.9.10 + py3.9.11 + # fixed in 3.9.12 + py3.10.0 + py3.10.1 + py3.10.2 + py3.10.3 + # fixed in 3.10.4 # PYSIDE-535: These errors are still present. Please try to remove one :) [sample::mixed_mi] pypy diff --git a/build_scripts/main.py b/build_scripts/main.py index 314b6d30..cc77c40b 100644 --- a/build_scripts/main.py +++ b/build_scripts/main.py @@ -611,9 +611,9 @@ class PysideBuild(_build, CommandMixin, BuildInfoCollectorMixin): # embedding_generator.py. Pass it as a separate option. cmake_cmd.append(f"-DQFP_PYTHON_HOST_PATH={sys.executable}") else: - cmake_cmd.append(f"-DPYTHON_EXECUTABLE={self.py_executable}") - cmake_cmd.append(f"-DPYTHON_INCLUDE_DIR={self.py_include_dir}") - cmake_cmd.append(f"-DPYTHON_LIBRARY={self.py_library}") + cmake_cmd.append(f"-DPython_EXECUTABLE={self.py_executable}") + cmake_cmd.append(f"-DPython_INCLUDE_DIR={self.py_include_dir}") + cmake_cmd.append(f"-DPython_LIBRARY={self.py_library}") # If a custom shiboken cmake config directory path was provided, pass it to CMake. if OPTION["SHIBOKEN_CONFIG_DIR"] and config.is_internal_pyside_build(): @@ -674,10 +674,7 @@ class PysideBuild(_build, CommandMixin, BuildInfoCollectorMixin): else: log.warning('numpy include directory was not found.') - if self.build_type.lower() == 'debug': - if not self.is_cross_compile: - cmake_cmd.append(f"-DPYTHON_DEBUG_LIBRARY={self.py_library}") - else: + if self.build_type.lower() != 'debug': if OPTION['NO_STRIP']: cmake_cmd.append("-DQFP_NO_STRIP=1") if OPTION['NO_OVERRIDE_OPTIMIZATION_FLAGS']: @@ -695,12 +692,14 @@ class PysideBuild(_build, CommandMixin, BuildInfoCollectorMixin): "(default yes if applicable, i.e. Python " "version >= 3.8 and release build if on Windows)") + if OPTION["DISABLE_PYI"]: + cmake_cmd.append("-DDISABLE_PYI=yes") + if OPTION["LOG_LEVEL"] == LogLevel.VERBOSE: cmake_cmd.append("-DCMAKE_VERBOSE_MAKEFILE:BOOL=ON") else: cmake_cmd.append("-DCMAKE_VERBOSE_MAKEFILE:BOOL=OFF") - if OPTION['COMPILER_LAUNCHER']: compiler_launcher = OPTION['COMPILER_LAUNCHER'] cmake_cmd.append(f"-DCMAKE_C_COMPILER_LAUNCHER={compiler_launcher}") @@ -859,8 +858,7 @@ class PysideBuild(_build, CommandMixin, BuildInfoCollectorMixin): if OPTION["LOG_LEVEL"] == LogLevel.VERBOSE and self.make_generator == "Ninja": make_doc_cmd.append("-v") if run_process(make_doc_cmd) != 0: - raise SetupError("Error generating documentation " - f"for {extension}") + raise SetupError(f"Error generating documentation for {extension}") else: log.info("Sphinx not found, skipping documentation build") else: @@ -1006,8 +1004,8 @@ class PysideBuild(_build, CommandMixin, BuildInfoCollectorMixin): # (lib/libclang.lib), whereas we want to copy the shared # library (bin/libclang.dll). clang_lib_path = Path(re.sub(r'lib/libclang.lib$', - 'bin/libclang.dll', - clang_lib_path)) + 'bin/libclang.dll', + clang_lib_path)) else: clang_lib_path = Path(clang_lib_path) # shiboken6 links against libclang.so.6 or a similarly @@ -1095,6 +1093,7 @@ class PysideBuild(_build, CommandMixin, BuildInfoCollectorMixin): message = "Patched rpath to '$ORIGIN/' in" if sys.platform.startswith('linux'): + def rpath_cmd(srcpath): final_rpath = '' # Command line rpath option takes precedence over @@ -1113,6 +1112,7 @@ class PysideBuild(_build, CommandMixin, BuildInfoCollectorMixin): elif sys.platform == 'darwin': message = "Updated rpath in" + def rpath_cmd(srcpath): final_rpath = '' # Command line rpath option takes precedence over @@ -1274,7 +1274,7 @@ class PysideRstDocs(Command, CommandMixin): if self.name == PYSIDE: self.sphinx_src = self.out_dir / "rst" example_gallery = config.setup_script_dir / "tools" / "example_gallery" / "main.py" - assert(example_gallery.is_file()) + assert example_gallery.is_file() example_gallery_cmd = [sys.executable, os.fspath(example_gallery)] if OPTION["LOG_LEVEL"] == LogLevel.QUIET: example_gallery_cmd.append("--quiet") diff --git a/build_scripts/options.py b/build_scripts/options.py index e49dd61c..6c6acd58 100644 --- a/build_scripts/options.py +++ b/build_scripts/options.py @@ -18,12 +18,13 @@ _AVAILABLE_MKSPECS = ["ninja", "msvc", "mingw"] if sys.platform == "win32" else ADDITIONAL_OPTIONS = """ Additional options: --limited-api Use Limited API [yes/no] - ---macos-use-libc++ Use libc++ on macOS + --macos-use-libc++ Use libc++ on macOS --snapshot-build Snapshot build --package-timestamp Package Timestamp --cmake-toolchain-file Path to CMake toolchain to enable cross-compiling --shiboken-host-path Path to host shiboken package when cross-compiling --qt-host-path Path to host Qt installation when cross-compiling + --disable-pyi Disable .pyi file generation """ @@ -159,6 +160,7 @@ OPTION = { "VERBOSE_BUILD": has_option('verbose-build'), "SNAPSHOT_BUILD": has_option("snapshot-build"), "LIMITED_API": option_value("limited-api"), + "DISABLE_PYI": has_option("disable-pyi"), "PACKAGE_TIMESTAMP": option_value("package-timestamp"), # This is used automatically by setuptools.command.install object, to # specify the final installation location. @@ -442,8 +444,8 @@ class CommandMixin(object): # qtpaths is available. This happens when building the host # tools in the overall cross-building process. use_cmake = False - if (using_cmake_toolchain_file or - (not self.qmake and not self.qtpaths and self.qt_target_path)): + if (using_cmake_toolchain_file or (not self.qmake + and not self.qtpaths and self.qt_target_path)): use_cmake = True QtInfo().setup(qtpaths_abs_path, self.cmake, qmake_abs_path, @@ -541,7 +543,8 @@ class CommandMixin(object): # explicitly. This is to help with the building of host tools # while cross-compiling. # Skip this process for the 'build_rst_docs' command - if not self.is_cross_compile and not self.qt_target_path and 'build_rst_docs' not in sys.argv: + if not (self.is_cross_compile and not self.qt_target_path + and 'build_rst_docs' not in sys.argv): # Enforce usage of qmake in QtInfo if it was given explicitly. if self.qmake: self.has_qmake_option = True diff --git a/build_scripts/platforms/linux.py b/build_scripts/platforms/linux.py index 835cc4dd..cb388e8a 100644 --- a/build_scripts/platforms/linux.py +++ b/build_scripts/platforms/linux.py @@ -19,7 +19,6 @@ def prepare_standalone_package_linux(pyside_build, _vars, cross_build=False, is_ copy_qml = True copy_translations = True copy_qt_conf = True - should_copy_icu_libs = True log.info("Copying files...") @@ -29,7 +28,6 @@ def prepare_standalone_package_linux(pyside_build, _vars, cross_build=False, is_ copy_qml = False copy_translations = False copy_qt_conf = False - should_copy_icu_libs = False # /lib/* -> /{st_package_name}/Qt/lib destination_dir = Path("{st_build_dir}/{st_package_name}".format(**_vars)) @@ -55,7 +53,7 @@ def prepare_standalone_package_linux(pyside_build, _vars, cross_build=False, is_ _filter=accepted_modules, recursive=False, _vars=_vars, force_copy_symlinks=True) - if should_copy_icu_libs and not cross_build and not is_android: + if not cross_build and not is_android: # Check if ICU libraries were copied over to the destination # Qt libdir. maybe_icu_libs = find_files_using_glob(destination_qt_lib_dir, "libicu*") diff --git a/build_scripts/platforms/unix.py b/build_scripts/platforms/unix.py index 6b4a3e95..17c4ba34 100644 --- a/build_scripts/platforms/unix.py +++ b/build_scripts/platforms/unix.py @@ -126,6 +126,7 @@ def prepare_packages_posix(pyside_build, _vars, cross_build=False): if sys.platform.startswith("linux"): scripts.append("android_deploy.py") + scripts.append("requirements-android.txt") script_dirs.extend(["deploy_lib/android", "deploy_lib/android/recipes/PySide6", "deploy_lib/android/recipes/shiboken6",]) @@ -140,7 +141,8 @@ def prepare_packages_posix(pyside_build, _vars, cross_build=False): src = f"{{install_dir}}/bin/{script_dir}" target = f"{{st_build_dir}}/{{st_package_name}}/scripts/{script_dir}" # Exclude subdirectory tests - copydir(src, target, _filter=["*.py", "*.spec", "*.jpg"], recursive=False, _vars=_vars) + copydir(src, target, _filter=["*.py", "*.spec", "*.jpg", "*.icns", "*.ico"], + recursive=False, _vars=_vars) # /bin/* -> {st_package_name}/ executables.extend(copydir( diff --git a/build_scripts/platforms/windows_desktop.py b/build_scripts/platforms/windows_desktop.py index 543ee988..c3b19d5a 100644 --- a/build_scripts/platforms/windows_desktop.py +++ b/build_scripts/platforms/windows_desktop.py @@ -120,7 +120,8 @@ def prepare_packages_win32(pyside_build, _vars): src = f"{{install_dir}}/bin/{script_dir}" target = f"{{st_build_dir}}/{{st_package_name}}/scripts/{script_dir}" # Exclude subdirectory tests - copydir(src, target, _filter=["*.py", "*.spec"], recursive=False, _vars=_vars) + copydir(src, target, _filter=["*.py", "*.spec", "*.jpg", "*.icns", "*.ico"], + recursive=False, _vars=_vars) # /bin/*.exe,*.dll -> {st_package_name}/ filters = ["pyside*.exe", "pyside*.dll"] diff --git a/build_scripts/wheel_files.py b/build_scripts/wheel_files.py index 15aa741e..ec66b957 100644 --- a/build_scripts/wheel_files.py +++ b/build_scripts/wheel_files.py @@ -157,7 +157,7 @@ def wheel_files_pyside_essentials() -> List[ModuleData]: module_QtWidgets(), module_QtHelp(), module_QtNetwork(), - module_QtConcurent(), + module_QtConcurrent(), module_QtDBus(), module_QtDesigner(), module_QtOpenGL(), @@ -383,7 +383,7 @@ def module_QtBluetooth() -> ModuleData: return data -def module_QtConcurent() -> ModuleData: +def module_QtConcurrent() -> ModuleData: data = ModuleData("Concurrent") return data diff --git a/coin/dependencies.yaml b/coin/dependencies.yaml index 8a1c4166..2bf2feb2 100644 --- a/coin/dependencies.yaml +++ b/coin/dependencies.yaml @@ -1,6 +1,6 @@ product_dependency: ../../qt/qt5: - ref: "e6ff9950e1d0365d2c4f231fd1b6fe194144fd52" + ref: "15b7e7434fd79334a5cf071e36cee7663fe1fb45" dependency_source: supermodule dependencies: [ "../../qt/qt3d", diff --git a/coin/instructions/common_environment.yaml b/coin/instructions/common_environment.yaml index ab1c45ef..8cb3c1d5 100644 --- a/coin/instructions/common_environment.yaml +++ b/coin/instructions/common_environment.yaml @@ -250,7 +250,7 @@ instructions: equals_value: Linux - type: EnvironmentVariable variableName: interpreter - variableValue: "python3.8" + variableValue: "python3.11" enable_if: condition: property property: host.osVersion diff --git a/coin_build_instructions.py b/coin_build_instructions.py index de3b9a8c..0f278dbc 100644 --- a/coin_build_instructions.py +++ b/coin_build_instructions.py @@ -12,8 +12,8 @@ from build_scripts.log import log from build_scripts.options import has_option, option_value from build_scripts.utils import (expand_clang_variables, get_ci_qtpaths_path, get_qtci_virtualEnv, - parse_cmake_conf_assignments_by_key, remove_tree, - run_instruction) + parse_cmake_conf_assignments_by_key, + remove_tree, run_instruction) log.setLevel(logging.INFO) @@ -27,20 +27,22 @@ CI_ENV_INSTALL_DIR = option_value("instdir") CI_ENV_AGENT_DIR = option_value("agentdir") CI_COMPILER = option_value("compiler") CI_USE_SCCACHE = option_value("compiler-launcher") -CI_INTEGRATION_ID = option_value("coinIntegrationId") or str(calendar.timegm(datetime.datetime.now().timetuple())) +CI_INTEGRATION_ID = option_value("coinIntegrationId") or str( + calendar.timegm(datetime.datetime.now().timetuple()) +) CI_FEATURES = [] _ci_features = option_value("features") if _ci_features is not None: - for f in _ci_features.split(', '): + for f in _ci_features.split(", "): CI_FEATURES.append(f) CI_RELEASE_CONF = has_option("packaging") CI_TEST_PHASE = option_value("phase") -if CI_TEST_PHASE not in ["ALL", "BUILD", "WHEEL"]: +if CI_TEST_PHASE not in ["ALL", "BUILD"]: CI_TEST_PHASE = "ALL" def get_current_script_path(): - """ Returns the absolute path containing this script. """ + """Returns the absolute path containing this script.""" try: this_file = __file__ except NameError: @@ -64,8 +66,8 @@ def is_snapshot_build(): pyside_project_dir = os.path.join(setup_script_dir, "sources", "pyside6") d = parse_cmake_conf_assignments_by_key(pyside_project_dir) - release_version_type = d.get('pyside_PRE_RELEASE_VERSION_TYPE') - pre_release_version = d.get('pyside_PRE_RELEASE_VERSION') + release_version_type = d.get("pyside_PRE_RELEASE_VERSION_TYPE") + pre_release_version = d.get("pyside_PRE_RELEASE_VERSION") if pre_release_version and release_version_type: return True return False @@ -75,7 +77,9 @@ def call_setup(python_ver, phase): print("call_setup") print("python_ver", python_ver) print("phase", phase) - _pExe, _env, env_pip, env_python = get_qtci_virtualEnv(python_ver, CI_HOST_OS, CI_HOST_ARCH, CI_TARGET_ARCH) + _pExe, _env, env_pip, env_python = get_qtci_virtualEnv( + python_ver, CI_HOST_OS, CI_HOST_ARCH, CI_TARGET_ARCH + ) if phase in ["BUILD"]: remove_tree(_env, True) @@ -84,7 +88,10 @@ def call_setup(python_ver, phase): python3 = "python3" if sys.platform == "win32": python3 = os.path.join(os.getenv("PYTHON3_PATH"), "python.exe") - run_instruction([python3, "-m", "pip", "install", "--user", "virtualenv==20.7.2"], "Failed to pin virtualenv") + run_instruction( + [python3, "-m", "pip", "install", "--user", "virtualenv==20.7.2"], + "Failed to pin virtualenv", + ) # installing to user base might not be in PATH by default. env_path = os.path.join(site.USER_BASE, "bin") v_env = os.path.join(env_path, "virtualenv") @@ -99,17 +106,17 @@ def call_setup(python_ver, phase): v_env = "virtualenv" run_instruction([str(v_env), "-p", str(_pExe), str(_env)], "Failed to create virtualenv") # When the 'python_ver' variable is empty, we are using Python 2 - # Pip is always upgraded when CI template is provisioned, upgrading it in later phase may cause perm issue - run_instruction([str(env_pip), "install", "-r", "requirements.txt"], "Failed to install dependencies") + # Pip is always upgraded when CI template is provisioned, + # upgrading it in later phase may cause perm issue + run_instruction( + [str(env_pip), "install", "-r", "requirements.txt"], "Failed to install dependencies" + ) cmd = [env_python, "-u", "setup.py"] if phase in ["BUILD"]: cmd += ["build", "--standalone", "--unity"] - elif phase in ["WHEEL"] or CI_RELEASE_CONF: - cmd += ["bdist_wheel", "--reuse-build", "--standalone", "--skip-cmake", "--skip-make-install", "--only-package"] - cmd += ["--build-tests", - "--log-level=verbose"] + cmd += ["--build-tests", "--log-level=verbose"] if CI_TARGET_ARCH == "X86_64-ARM64": cmd += ["--macos-arch='x86_64;arm64'"] @@ -137,20 +144,20 @@ def call_setup(python_ver, phase): env = os.environ run_instruction(cmd, "Failed to run setup.py for build", initial_env=env) -if __name__ == "__main__": +if __name__ == "__main__": # Remove some environment variables that impact cmake - arch = '32' if CI_TARGET_ARCH == 'X86' else '64' + arch = "32" if CI_TARGET_ARCH == "X86" else "64" expand_clang_variables(arch) - for env_var in ['CC', 'CXX']: + for env_var in ["CC", "CXX"]: if os.environ.get(env_var): del os.environ[env_var] python_ver = "3" - if CI_TARGET_OS in ["Linux"] and CI_HOST_ARCH !="aarch64": - python_ver = "3.8" + if CI_TARGET_OS in ["Linux"] and CI_HOST_ARCH != "aarch64": + python_ver = "3.11" wheel_package_dir = "qfpa-p3.6" if CI_TARGET_OS in ["Windows"]: - if (os.environ.get('HOST_OSVERSION_COIN')).startswith('windows_10'): + if (os.environ.get("HOST_OSVERSION_COIN")).startswith("windows_10"): python_ver = "3.10.0" else: python_ver = "3.8.1" @@ -160,8 +167,4 @@ if __name__ == "__main__": if os.environ.get("QTEST_ENVIRONMENT") == "ci" and sys.platform == "win32": signing_dir = str(os.environ.get("PYSIDE_SIGNING_DIR")) print("Check for signing dir " + signing_dir) - assert(os.path.isdir(signing_dir)) - if CI_TEST_PHASE in ["ALL", "WHEEL"] and sys.platform != "win32": - # "Old" Windows wheels won't be signed anyway so there is no need to - # create those, so that we don't accidentally release those. - call_setup(python_ver, "WHEEL") + assert os.path.isdir(signing_dir) diff --git a/coin_test_instructions.py b/coin_test_instructions.py index 03959a53..219fcf37 100644 --- a/coin_test_instructions.py +++ b/coin_test_instructions.py @@ -6,7 +6,7 @@ import site import sys from build_scripts.log import log -from build_scripts.options import has_option, log, option_value +from build_scripts.options import has_option, option_value from build_scripts.utils import (expand_clang_variables, get_ci_qmake_path, get_qtci_virtualEnv, remove_tree, run_instruction) @@ -30,7 +30,8 @@ CI_RELEASE_CONF = has_option("packaging") def call_testrunner(python_ver, buildnro): - _pExe, _env, env_pip, env_python = get_qtci_virtualEnv(python_ver, CI_HOST_OS, CI_HOST_ARCH, CI_TARGET_ARCH) + _pExe, _env, env_pip, env_python = get_qtci_virtualEnv(python_ver, CI_HOST_OS, CI_HOST_ARCH, + CI_TARGET_ARCH) remove_tree(_env, True) # Pinning the virtualenv before creating one # Use pip3 if possible while pip seems to install the virtualenv to wrong dir in some OS @@ -42,9 +43,11 @@ def call_testrunner(python_ver, buildnro): if CI_HOST_OS == "MacOS" and CI_HOST_ARCH == "ARM64": v_env = "virtualenv" run_instruction([str(v_env), "-p", str(_pExe), str(_env)], "Failed to create virtualenv") - run_instruction([env_pip, "install", "-r", "requirements.txt"], "Failed to install dependencies") + run_instruction([env_pip, "install", "-r", "requirements.txt"], + "Failed to install dependencies") else: - run_instruction([python3, "-m", "pip", "install", "--user", "virtualenv==20.7.2"], "Failed to pin virtualenv") + run_instruction([python3, "-m", "pip", "install", "--user", "virtualenv==20.7.2"], + "Failed to pin virtualenv") # installing to user base might not be in PATH by default. env_path = os.path.join(site.USER_BASE, "bin") v_env = os.path.join(env_path, "virtualenv") @@ -59,8 +62,10 @@ def call_testrunner(python_ver, buildnro): v_env = "virtualenv" run_instruction([str(v_env), "-p", str(_pExe), str(_env)], "Failed to create virtualenv") # When the 'python_ver' variable is empty, we are using Python 2 - # Pip is always upgraded when CI template is provisioned, upgrading it in later phase may cause perm issue - run_instruction([env_pip, "install", "-r", "requirements.txt"], "Failed to install dependencies") + # Pip is always upgraded when CI template is provisioned, + # upgrading it in later phase may cause perm issue + run_instruction([env_pip, "install", "-r", "requirements.txt"], + "Failed to install dependencies") # Install distro to replace missing platform.linux_distribution() in python3.8 run_instruction([env_pip, "install", "distro"], "Failed to install distro") @@ -73,18 +78,8 @@ def call_testrunner(python_ver, buildnro): # Try to install built wheels, and build some buildable examples. if CI_RELEASE_CONF: wheel_tester_path = os.path.join("testing", "wheel_tester.py") - # We create wheels differently in Qt CI with Windows and there are no "old" wheels - if CI_HOST_OS != "Windows": - # Run the test for the old set of wheels - cmd = [env_python, wheel_tester_path, qmake_path] - run_instruction(cmd, "Error while running wheel_tester.py on old wheels") - - # Uninstalling the other wheels - run_instruction([env_pip, "uninstall", "shiboken6", "shiboken6_generator", "pyside6", "-y"], - "Failed to uninstall old wheels") - # Run the test for the new set of wheels - cmd = [env_python, wheel_tester_path, qmake_path, "--wheels-dir=dist_new", "--new"] + cmd = [env_python, wheel_tester_path, qmake_path, "--wheels-dir=dist", "--new"] run_instruction(cmd, "Error while running wheel_tester.py on new wheels") @@ -106,7 +101,7 @@ def run_test_instructions(): else: call_testrunner("3.8.1", str(testRun)) elif CI_HOST_OS == "Linux": - call_testrunner("3.8", str(testRun)) + call_testrunner("3.11", str(testRun)) else: call_testrunner("3", str(testRun)) diff --git a/create_wheels.py b/create_wheels.py index 46e218f1..1b34fa40 100644 --- a/create_wheels.py +++ b/create_wheels.py @@ -12,6 +12,8 @@ from shutil import copy, rmtree, copytree from typing import List, Optional, Tuple import build # type: ignore +import pyproject_hooks +import build_scripts.wheel_files from build_scripts.wheel_files import (ModuleData, # type: ignore set_pyside_package_path, wheel_files_pyside_addons, @@ -20,6 +22,7 @@ from build_scripts.utils import available_pyside_tools PACKAGE_FOR_WHEELS = "package_for_wheels" +PYSIDE_DESCRIPTION = "Python bindings for the Qt cross-platform application and UI framework" @dataclass @@ -134,8 +137,28 @@ def get_platform_tag() -> str: def generate_pyproject_toml(artifacts: Path, setup: SetupData) -> str: content = None + _name = setup.name _tag = get_platform_tag() + _console_scripts = "" + if setup.console_scripts: + _formatted_console_scripts = "\n".join(setup.console_scripts) + _console_scripts = f"[project.scripts]\n{_formatted_console_scripts}" + + # Installing dependencies + _dependencies = [] + if _name in ("PySide6", "PySide6_Examples"): + _dependencies.append(f"shiboken6=={setup.version[0]}") + _dependencies.append(f"PySide6_Essentials=={setup.version[0]}") + _dependencies.append(f"PySide6_Addons=={setup.version[0]}") + elif _name == "PySide6_Essentials": + _dependencies.append(f"shiboken6=={setup.version[0]}") + elif _name == "PySide6_Addons": + _dependencies.append(f"shiboken6=={setup.version[0]}") + _dependencies.append(f"PySide6_Essentials=={setup.version[0]}") + elif _name == "shiboken6_generator": + _dependencies.append(f"shiboken6=={setup.version[0]}") + with open(artifacts / "pyproject.toml.base") as f: content = ( f.read() @@ -144,8 +167,9 @@ def generate_pyproject_toml(artifacts: Path, setup: SetupData) -> str: .replace("PROJECT_DESCRIPTION", f'"{setup.description}"') .replace("PROJECT_README", f'"{setup.readme}"') .replace("PROJECT_TAG", f'"{_tag}"') + .replace("PROJECT_SCRIPTS", _console_scripts) + .replace("PROJECT_DEPENDENCIES", f"{_dependencies}") ) - return content @@ -164,18 +188,6 @@ def generate_setup_py(artifacts: Path, setup: SetupData): else: fext = "Shiboken" - # Installing dependencies - install_requires = [] - if name in ("PySide6", "PySide6_Examples"): - install_requires.append(f"shiboken6=={setup.version[0]}") - install_requires.append(f"PySide6_Essentials=={setup.version[0]}") - install_requires.append(f"PySide6_Addons=={setup.version[0]}") - elif _name == "PySide6_Essentials": - install_requires.append(f"shiboken6=={setup.version[0]}") - elif _name == "PySide6_Addons": - install_requires.append(f"shiboken6=={setup.version[0]}") - install_requires.append(f"PySide6_Essentials=={setup.version[0]}") - # For special wheels based on 'PySide6' # we force the name to be PySide6 for the package_name, # so we can take the files from that packaged-directory @@ -186,8 +198,6 @@ def generate_setup_py(artifacts: Path, setup: SetupData): content = f.read().format( name=_name, fake_ext=fext, - install=install_requires, - console_scripts={"console_scripts": setup.console_scripts}, ) return content @@ -200,8 +210,8 @@ def wheel_shiboken_generator(package_path: Path) -> Tuple[SetupData, None]: description="Python/C++ bindings generator", readme="README.shiboken6-generator.md", console_scripts=[ - "shiboken6 = shiboken6_generator.scripts.shiboken_tool:main", - "shiboken6-genpyi = shiboken6_generator.scripts.shiboken_tool:genpyi", + 'shiboken6 = "shiboken6_generator.scripts.shiboken_tool:main"', + 'shiboken6-genpyi = "shiboken6_generator.scripts.shiboken_tool:genpyi"', ], ) @@ -229,16 +239,16 @@ def wheel_pyside6_essentials(package_path: Path) -> Tuple[SetupData, List[Module # Also, the tool should not exist in any other platform than Linux _console_scripts = [] if ("android_deploy" in _pyside_tools) and sys.platform.startswith("linux"): - _console_scripts = ["pyside6-android-deploy = PySide6.scripts.pyside_tool:android_deploy"] + _console_scripts = ['pyside6-android-deploy = "PySide6.scripts.pyside_tool:android_deploy"'] _pyside_tools.remove("android_deploy") - _console_scripts.extend([f"pyside6-{tool} = PySide6.scripts.pyside_tool:{tool}" + _console_scripts.extend([f'pyside6-{tool} = "PySide6.scripts.pyside_tool:{tool}"' for tool in _pyside_tools]) setup = SetupData( name="PySide6_Essentials", version=get_version_from_package("PySide6", package_path), # we use 'PySide6' here - description="Python bindings for the Qt cross-platform application and UI framework (Essentials)", + description=f"{PYSIDE_DESCRIPTION} (Essentials)", readme="README.pyside6_essentials.md", console_scripts=_console_scripts ) @@ -252,7 +262,7 @@ def wheel_pyside6_addons(package_path: Path) -> Tuple[SetupData, List[ModuleData setup = SetupData( name="PySide6_Addons", version=get_version_from_package("PySide6", package_path), # we use 'PySide6' here - description="Python bindings for the Qt cross-platform application and UI framework (Addons)", + description=f"{PYSIDE_DESCRIPTION} (Addons)", readme="README.pyside6_addons.md", console_scripts=[], ) @@ -266,7 +276,7 @@ def wheel_pyside6(package_path: Path) -> Tuple[SetupData, Optional[List[ModuleDa setup = SetupData( name="PySide6", version=get_version_from_package("PySide6", package_path), - description="Python bindings for the Qt cross-platform application and UI framework", + description=PYSIDE_DESCRIPTION, readme="README.pyside6.md", console_scripts=[], ) @@ -326,6 +336,33 @@ def get_build_directory(options: Namespace): raise Exception("Unable to determine build directory, no matching virtual environment found") +def check_modules_consistency(): + available_functions = dir(build_scripts.wheel_files) + functions = [i.replace("module_", "") for i in available_functions if i.startswith("module_")] + + sources = [i.stem for i in Path("sources/pyside6/PySide6/").glob("Qt*")] + + missing_modules = set(sources) - set(functions) + + if len(missing_modules): + print("Warning: the following modules don't have a function " + f"in 'build_scripts/wheel_files.py':\n {missing_modules}") + + # Check READMEs + readme_modules = set() + for r in Path(".").glob("README.pyside6*"): + with open(r) as f: + for line in f: + if line.startswith("* Qt"): + readme_modules.add(line.strip().replace("* ", "")) + + missing_modules_readme = set(sources) - readme_modules + + if len(missing_modules_readme): + print("Warning: the following modules are not in READMEs :" + f"\n {missing_modules_readme}") + + if __name__ == "__main__": parser = ArgumentParser() @@ -344,6 +381,10 @@ if __name__ == "__main__": ) options = parser.parse_args() + # Sanity check between the available modules, + # and the functions in build_scripts/wheel_files.py + check_modules_consistency() + build_directory = get_build_directory(options) verbose = False @@ -423,18 +464,18 @@ if __name__ == "__main__": # 5. call the build module to create the wheel print("-- Creating wheels") if not verbose: - _runner = build.pep517.wrappers.quiet_subprocess_runner + _runner = pyproject_hooks.quiet_subprocess_runner else: - _runner = build.pep517.wrappers.default_subprocess_runner + _runner = pyproject_hooks.default_subprocess_runner builder = build.ProjectBuilder(package_path, runner=_runner) - builder.build("wheel", "dist_new") + builder.build("wheel", "dist") - # 7. Copy wheels back - print("-- Copying wheels to dist_new/") - dist_path = Path("dist_new") + # 6. Copy wheels back + print("-- Copying wheels to dist/") + dist_path = Path("dist") if not dist_path.is_dir(): dist_path.mkdir() - for wheel in Path(package_path / "dist_new").glob("*.whl"): + for wheel in Path(package_path / "dist").glob("*.whl"): copy(wheel, dist_path / wheel.name) # 8. Remove leftover files diff --git a/doc/changelogs/changes-6.6.1 b/doc/changelogs/changes-6.6.1 index 26356926..64058de7 100644 --- a/doc/changelogs/changes-6.6.1 +++ b/doc/changelogs/changes-6.6.1 @@ -21,8 +21,7 @@ information about a particular change. (PySide6_Examples). - [PYSIDE-748] An optional parameter "tag" has been added to @Slot, allowing to set QMetaMethod.tag(). - - [PYSIDE-2500] QMetaMethod.invoke() has been added. The invocation functions - of QMetaObject now accept up to 10 arguments. + - [PYSIDE-769] QtAsyncio: Support for ThreadPoolExecutor has been added. - [PYSIDE-841] QtQuick3D procedural texture and QRhi window examples have been added. - [PYSIDE-2230] Support for Python 3.12 has been added. @@ -31,6 +30,8 @@ information about a particular change. - [PYSIDE-2485] A QLocale-related crash on macOS has been fixed. - [PYSIDE-2487] Error messages around signal/slot connections have been improved. - [PYSIDE-2494] The install location of QtAsyncio for CMake builds has been fixed. + - [PYSIDE-2500] QMetaMethod.invoke() has been added. The invocation functions + of QMetaObject now accept up to 10 arguments. - [PYSIDE-2509] The signature of SignalInstance.connect() has been fixed. - [PYSIDE-2510] An error is now set when instantiating a Signal on a non-QObject. - [PYSIDE-2514] The type hints of QAbstractItemView.setModel()/QGraphicsView.setScene() diff --git a/doc/changelogs/changes-6.6.2 b/doc/changelogs/changes-6.6.2 new file mode 100644 index 00000000..41ee1f16 --- /dev/null +++ b/doc/changelogs/changes-6.6.2 @@ -0,0 +1,66 @@ +Qt for Python 6.6.2 is a bug-fix release. + +For more details, refer to the online documentation included in this +distribution. The documentation is also available online: + +https://doc.qt.io/qtforpython/ + +Some of the changes listed in this file include issue tracking numbers +corresponding to tasks in the Qt Bug Tracker: + +https://bugreports.qt.io/ + +Each of these identifiers can be entered in the bug tracker to obtain more +information about a particular change. + +**************************************************************************** +* PySide6 * +**************************************************************************** + + - [PYSIDE-535] PyPySide has been updated to Python 3.10 + - [PYSIDE-769] QtAsyncio: call_soon_threadsafe() and QtAsyncio.run() (with + [PYSIDE-1112] keep_running and quit_qapp arguments) have been implemented. + [PYSIDE-2561] A bug with Python 3.12 has been fixed. Documentation has + [PYSIDE-2575] been added. Keyboard interrupts are now caught. Cancelling + waiting tasks has been fixed. Tasks are now awaitable. + - [PYSIDE-1106] Documentation: The generation process has been optimized + and warnings have been reduced. + - [PYSIDE-1586] Qt Charts: class QColorAxis has been added. + - [PYSIDE-1612] Android Deployment: The QtQuick dependency is now added + when present. + - [PYSIDE-1612] Deployment problems when using pyenv have been fixed. + - [PYSIDE-1612] Nuitka has been upgraded to 1.8.0 + - [PYSIDE-1612] Deployment: An icon for the application has been added. + - [PYSIDE-1612] A separate requirements.txt for Android deployment has been + added. + - [PYSIDE-1612] Android Deployment: `buildozer` is now installed with the + --init cli argument + - [PYSIDE-1931] Further hangs in QSql(Relational)TableModel.select() (PostGres + with SSH tunnel) have been fixed. + - [PYSIDE-2206] Examples quick3d/proceduraltexture, sql/books have been + updated. The contactslist has been added. Examples supported + on Android are tagged as `Android`. + - [PYSIDE-2439] FindPython is now used instead of the deprecated + FindPythonInterp and FindPythonLibs CMake modules. + - [PYSIDE-2539] Documentation: The enum name is now generated for flags. + - [PYSIDE-2544] A crash accessing QDBusVariant.variant() has been fixed. + - [PYSIDE-2547] A hang in QDBusConnection.connect() has been fixed. + - [PYSIDE-2558] Documentation: the differences between commercial and lts + releases have been clarified. + - [PYSIDE-2568] A crash of pyside6-designer with pyenv on Unix has been + fixed. + - [PYSIDE-2574] Documentation: A favicon for browser tabs has been added. + +**************************************************************************** +* Shiboken6 * +**************************************************************************** + + - [PYSIDE-1106] Documentation: An option to disable the inheritance diagram + has been added. + - [PYSIDE-1735] An error in the generated code when all enum values are + deprecated has been fixed. + - [PYSIDE-2404] pyi file generation can now be disabled for debugging. + - [PYSIDE-2530] yocto cross builds have been fixed to find + shiboken_wrapper.sh. + - [PYSIDE-2577] Documentation/doxygen: A bug querying const functions + has been fixed. diff --git a/examples/async/eratosthenes/eratosthenes_asyncio.py b/examples/async/eratosthenes/eratosthenes_asyncio.py index 0cb41981..598d9b4b 100644 --- a/examples/async/eratosthenes/eratosthenes_asyncio.py +++ b/examples/async/eratosthenes/eratosthenes_asyncio.py @@ -5,10 +5,9 @@ from PySide6.QtCore import (Qt, QObject, Signal, Slot) from PySide6.QtGui import (QColor, QFont, QPalette) from PySide6.QtWidgets import (QApplication, QGridLayout, QLabel, QMainWindow, QVBoxLayout, QWidget) -from PySide6.QtAsyncio import QAsyncioEventLoopPolicy +import PySide6.QtAsyncio as QtAsyncio import asyncio -import signal import sys from random import randint @@ -132,8 +131,4 @@ if __name__ == "__main__": main_window.show() - signal.signal(signal.SIGINT, signal.SIG_DFL) - - asyncio.set_event_loop_policy(QAsyncioEventLoopPolicy()) - asyncio.ensure_future(eratosthenes.start()) - asyncio.get_event_loop().run_forever() + QtAsyncio.run(eratosthenes.start()) diff --git a/examples/async/minimal/minimal_asyncio.py b/examples/async/minimal/minimal_asyncio.py index a66e07ef..4545f35d 100644 --- a/examples/async/minimal/minimal_asyncio.py +++ b/examples/async/minimal/minimal_asyncio.py @@ -4,10 +4,9 @@ from PySide6.QtCore import (Qt, QObject, Signal, Slot) from PySide6.QtWidgets import (QApplication, QLabel, QMainWindow, QPushButton, QVBoxLayout, QWidget) -from PySide6.QtAsyncio import QAsyncioEventLoopPolicy +import PySide6.QtAsyncio as QtAsyncio import asyncio -import signal import sys @@ -60,7 +59,4 @@ if __name__ == "__main__": main_window.show() - signal.signal(signal.SIGINT, signal.SIG_DFL) - - asyncio.set_event_loop_policy(QAsyncioEventLoopPolicy()) - asyncio.get_event_loop().run_forever() + QtAsyncio.run() diff --git a/examples/axcontainer/axviewer/axviewer.py b/examples/axcontainer/axviewer/axviewer.py index abc0599e..bcd4c271 100644 --- a/examples/axcontainer/axviewer/axviewer.py +++ b/examples/axcontainer/axviewer/axviewer.py @@ -7,7 +7,7 @@ import sys from PySide6.QtAxContainer import QAxSelect, QAxWidget from PySide6.QtGui import QAction from PySide6.QtWidgets import (QApplication, QDialog, - QMainWindow, QMessageBox, QToolBar) + QMainWindow, QMessageBox, QToolBar) class MainWindow(QMainWindow): @@ -25,7 +25,7 @@ class MainWindow(QMainWindow): fileMenu.addAction(exitAction) aboutMenu = self.menuBar().addMenu("&About") - aboutQtAct = QAction("About &Qt", self, triggered=qApp.aboutQt) + aboutQtAct = QAction("About &Qt", self, triggered=qApp.aboutQt) # noqa: F821 aboutMenu.addAction(aboutQtAct) self.axWidget = QAxWidget() self.setCentralWidget(self.axWidget) diff --git a/examples/bluetooth/btscanner/device.py b/examples/bluetooth/btscanner/device.py index e51160b8..c75f5b8a 100644 --- a/examples/bluetooth/btscanner/device.py +++ b/examples/bluetooth/btscanner/device.py @@ -46,7 +46,7 @@ class DeviceDiscoveryDialog(QDialog): item = QListWidgetItem(label) pairing_status = self._local_device.pairingStatus(info.address()) if (pairing_status == QBluetoothLocalDevice.Paired - or pairing_status == QBluetoothLocalDevice.AuthorizedPaired): + or pairing_status == QBluetoothLocalDevice.AuthorizedPaired): item.setForeground(QColor(Qt.green)) else: item.setForeground(QColor(Qt.black)) @@ -123,7 +123,8 @@ class DeviceDiscoveryDialog(QDialog): items = self._ui.list.findItems(address.toString(), Qt.MatchContains) color = QColor(Qt.red) - if pairing == QBluetoothLocalDevice.Paired or pairing == QBluetoothLocalDevice.AuthorizedPaired: + if (pairing == QBluetoothLocalDevice.Paired + or pairing == QBluetoothLocalDevice.AuthorizedPaired): color = QColor(Qt.green) for item in items: item.setForeground(color) diff --git a/examples/bluetooth/heartrate_game/connectionhandler.py b/examples/bluetooth/heartrate_game/connectionhandler.py index 79f2720e..421446c8 100644 --- a/examples/bluetooth/heartrate_game/connectionhandler.py +++ b/examples/bluetooth/heartrate_game/connectionhandler.py @@ -5,7 +5,7 @@ import sys from PySide6.QtBluetooth import QBluetoothLocalDevice from PySide6.QtQml import QmlElement -from PySide6.QtCore import QObject, Property, Signal, Slot, Qt, QCoreApplication +from PySide6.QtCore import QObject, Property, Signal, Slot, Qt from heartrate_global import simulator, is_android @@ -61,9 +61,9 @@ class ConnectionHandler(QObject): if is_android: permission = QBluetoothPermission() permission.setCommunicationModes(QBluetoothPermission.Access) - permission_status = qApp.checkPermission(permission) + permission_status = qApp.checkPermission(permission) # noqa: F821 if permission_status == Qt.PermissionStatus.Undetermined: - qApp.requestPermission(permission, self, self.initLocalDevice) + qApp.requestPermission(permission, self, self.initLocalDevice) # noqa: F821 return if permission_status == Qt.PermissionStatus.Denied: return diff --git a/examples/bluetooth/heartrate_game/devicefinder.py b/examples/bluetooth/heartrate_game/devicefinder.py index db42c2fa..5cabd1b5 100644 --- a/examples/bluetooth/heartrate_game/devicefinder.py +++ b/examples/bluetooth/heartrate_game/devicefinder.py @@ -4,7 +4,7 @@ from PySide6.QtBluetooth import (QBluetoothDeviceDiscoveryAgent, QBluetoothDeviceInfo) from PySide6.QtQml import QmlElement -from PySide6.QtCore import QTimer, Property, Signal, Slot, Qt, QCoreApplication +from PySide6.QtCore import QTimer, Property, Signal, Slot, Qt from bluetoothbaseclass import BluetoothBaseClass from deviceinfo import DeviceInfo @@ -49,9 +49,9 @@ class DeviceFinder(BluetoothBaseClass): if is_android: permission = QBluetoothPermission() permission.setCommunicationModes(QBluetoothPermission.Access) - permission_status = qApp.checkPermission(permission) + permission_status = qApp.checkPermission(permission) # noqa: F821 if permission_status == Qt.PermissionStatus.Undetermined: - qApp.requestPermission(permission, self, self.startSearch) + qApp.requestPermission(permission, self, self.startSearch) # noqa: F82 1 return elif permission_status == Qt.PermissionStatus.Denied: return diff --git a/examples/bluetooth/heartrate_game/devicehandler.py b/examples/bluetooth/heartrate_game/devicehandler.py index 85d160c4..df34052b 100644 --- a/examples/bluetooth/heartrate_game/devicehandler.py +++ b/examples/bluetooth/heartrate_game/devicehandler.py @@ -105,7 +105,7 @@ class DeviceHandler(BluetoothBaseClass): # Disconnect and delete old connection if self.m_control: self.m_control.disconnectFromDevice() - m_control = None + self.m_control = None # Create new controller and connect it if device available if self.m_currentDevice: @@ -166,7 +166,8 @@ class DeviceHandler(BluetoothBaseClass): #! [Filter HeartRate service 2] # If heartRateService found, create new service if self.m_foundHeartRateService: - self.m_service = self.m_control.createServiceObject(QBluetoothUuid(QBluetoothUuid.ServiceClassUuid.HeartRate), self) + self.m_service = self.m_control.createServiceObject( + QBluetoothUuid(QBluetoothUuid.ServiceClassUuid.HeartRate), self) if self.m_service: self.m_service.stateChanged.connect(self.serviceStateChanged) @@ -185,9 +186,11 @@ class DeviceHandler(BluetoothBaseClass): self.info = "Discovering services..." elif switch == QLowEnergyService.RemoteServiceDiscovered: self.info = "Service discovered." - hrChar = self.m_service.characteristic(QBluetoothUuid(QBluetoothUuid.CharacteristicType.HeartRateMeasurement)) + hrChar = self.m_service.characteristic( + QBluetoothUuid(QBluetoothUuid.CharacteristicType.HeartRateMeasurement)) if hrChar.isValid(): - self.m_notificationDesc = hrChar.descriptor(QBluetoothUuid.DescriptorType.ClientCharacteristicConfiguration) + self.m_notificationDesc = hrChar.descriptor( + QBluetoothUuid.DescriptorType.ClientCharacteristicConfiguration) if self.m_notificationDesc.isValid(): self.m_service.writeDescriptor(self.m_notificationDesc, QByteArray.fromHex(b"0100")) @@ -233,7 +236,7 @@ class DeviceHandler(BluetoothBaseClass): @Slot(QLowEnergyCharacteristic, QByteArray) def confirmedDescriptorWrite(self, d, value): if (d.isValid() and d == self.m_notificationDesc - and value == QByteArray.fromHex(b"0000")): + and value == QByteArray.fromHex(b"0000")): # disabled notifications . assume disconnect intent self.m_control.disconnectFromDevice() self.m_service = None @@ -244,7 +247,7 @@ class DeviceHandler(BluetoothBaseClass): # disable notifications if (self.m_notificationDesc.isValid() and self.m_service - and self.m_notificationDesc.value() == QByteArray.fromHex(b"0100")): + and self.m_notificationDesc.value() == QByteArray.fromHex(b"0100")): self.m_service.writeDescriptor(self.m_notificationDesc, QByteArray.fromHex(b"0000")) else: @@ -301,6 +304,6 @@ class DeviceHandler(BluetoothBaseClass): self.m_sum += value self.m_avg = float(self.m_sum) / len(self.m_measurements) self.m_calories = ((-55.0969 + (0.6309 * self.m_avg) + (0.1988 * 94) - + (0.2017 * 24)) / 4.184) * 60 * self.time / 3600 + + (0.2017 * 24)) / 4.184) * 60 * self.time / 3600 self.statsChanged.emit() diff --git a/examples/bluetooth/heartrate_game/doc/heartrate_game.rst b/examples/bluetooth/heartrate_game/doc/heartrate_game.rst index 0a0938ca..9d190d99 100644 --- a/examples/bluetooth/heartrate_game/doc/heartrate_game.rst +++ b/examples/bluetooth/heartrate_game/doc/heartrate_game.rst @@ -1,6 +1,8 @@ Bluetooth Low Energy Heart Rate Game ==================================== +.. tags:: Android + The Bluetooth Low Energy Heart Rate Game shows how to develop a Bluetooth Low Energy application using the Qt Bluetooth API. The application covers the scanning for Bluetooth Low Energy devices, diff --git a/examples/bluetooth/heartrate_game/heartrate_global.py b/examples/bluetooth/heartrate_game/heartrate_global.py index dec6f83a..584c44d2 100644 --- a/examples/bluetooth/heartrate_game/heartrate_global.py +++ b/examples/bluetooth/heartrate_game/heartrate_global.py @@ -1,7 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause import os -import sys _simulator = False @@ -16,4 +15,5 @@ def set_simulator(s): global _simulator _simulator = s + is_android = os.environ.get('ANDROID_ARGUMENT') diff --git a/examples/bluetooth/heartrate_server/heartrate_server.py b/examples/bluetooth/heartrate_server/heartrate_server.py index f98cc6fe..abbf4eb7 100644 --- a/examples/bluetooth/heartrate_server/heartrate_server.py +++ b/examples/bluetooth/heartrate_server/heartrate_server.py @@ -39,8 +39,8 @@ if __name__ == '__main__': char_data.setUuid(QBluetoothUuid.CharacteristicType.HeartRateMeasurement) char_data.setValue(QByteArray(2, 0)) char_data.setProperties(QLowEnergyCharacteristic.Notify) - client_config = QLowEnergyDescriptorData(QBluetoothUuid.DescriptorType.ClientCharacteristicConfiguration, - QByteArray(2, 0)) + client_config = QLowEnergyDescriptorData( + QBluetoothUuid.DescriptorType.ClientCharacteristicConfiguration, QByteArray(2, 0)) char_data.addDescriptor(client_config) service_data = QLowEnergyServiceData() @@ -66,8 +66,9 @@ if __name__ == '__main__': value = QByteArray() value.append(chr(0)) # Flags that specify the format of the value. value.append(chr(current_heart_rate)) # Actual value. - characteristic = service.characteristic(QBluetoothUuid.CharacteristicType.HeartRateMeasurement) - assert(characteristic.isValid()) + characteristic = service.characteristic( + QBluetoothUuid.CharacteristicType.HeartRateMeasurement) + assert characteristic.isValid() # Potentially causes notification. service.writeCharacteristic(characteristic, value) if current_heart_rate == 60: diff --git a/examples/bluetooth/lowenergyscanner/characteristicinfo.py b/examples/bluetooth/lowenergyscanner/characteristicinfo.py index a0e9df77..42bde875 100644 --- a/examples/bluetooth/lowenergyscanner/characteristicinfo.py +++ b/examples/bluetooth/lowenergyscanner/characteristicinfo.py @@ -85,4 +85,3 @@ class CharacteristicInfo(QObject): def characteristic(self, characteristic): self._characteristic = characteristic self.characteristic_changed.emit() - diff --git a/examples/bluetooth/lowenergyscanner/device.py b/examples/bluetooth/lowenergyscanner/device.py index e69a8450..09108cf6 100644 --- a/examples/bluetooth/lowenergyscanner/device.py +++ b/examples/bluetooth/lowenergyscanner/device.py @@ -13,6 +13,7 @@ from characteristicinfo import CharacteristicInfo QML_IMPORT_NAME = "Scanner" QML_IMPORT_MAJOR_VERSION = 1 + @QmlElement @QmlSingleton class Device(QObject): @@ -275,5 +276,3 @@ class Device(QObject): def stop_device_discovery(self): if self.discovery_agent.isActive(): self.discovery_agent.stop() - - diff --git a/examples/bluetooth/lowenergyscanner/deviceinfo.py b/examples/bluetooth/lowenergyscanner/deviceinfo.py index edcbef89..35a56882 100644 --- a/examples/bluetooth/lowenergyscanner/deviceinfo.py +++ b/examples/bluetooth/lowenergyscanner/deviceinfo.py @@ -32,4 +32,3 @@ class DeviceInfo(QObject): def set_device(self, device): self._device = device self.device_changed.emit() - diff --git a/examples/bluetooth/lowenergyscanner/doc/lowenergyscanner.rst b/examples/bluetooth/lowenergyscanner/doc/lowenergyscanner.rst index c20c2d8b..a0c57435 100644 --- a/examples/bluetooth/lowenergyscanner/doc/lowenergyscanner.rst +++ b/examples/bluetooth/lowenergyscanner/doc/lowenergyscanner.rst @@ -1,6 +1,8 @@ Bluetooth Low Energy Scanner Example ==================================== +.. tags:: Android + A Python application that demonstrates the analogous example in Qt `Bluetooth Low Energy Scanner `_ diff --git a/examples/bluetooth/lowenergyscanner/main.py b/examples/bluetooth/lowenergyscanner/main.py index dfbff97b..ec12f99e 100644 --- a/examples/bluetooth/lowenergyscanner/main.py +++ b/examples/bluetooth/lowenergyscanner/main.py @@ -10,7 +10,7 @@ from PySide6.QtCore import QCoreApplication from PySide6.QtGui import QGuiApplication from PySide6.QtQml import QQmlApplicationEngine -from device import Device +from device import Device # noqa: F401 from pathlib import Path if __name__ == '__main__': diff --git a/examples/bluetooth/lowenergyscanner/serviceinfo.py b/examples/bluetooth/lowenergyscanner/serviceinfo.py index 092e9898..cddffe66 100644 --- a/examples/bluetooth/lowenergyscanner/serviceinfo.py +++ b/examples/bluetooth/lowenergyscanner/serviceinfo.py @@ -62,5 +62,3 @@ class ServiceInfo(QObject): @service.setter def service(self, service): self._service = service - - diff --git a/examples/charts/callout/callout.py b/examples/charts/callout/callout.py index 3de00b8d..622938d1 100644 --- a/examples/charts/callout/callout.py +++ b/examples/charts/callout/callout.py @@ -4,8 +4,8 @@ """PySide6 port of the Callout example from Qt v5.x""" import sys -from PySide6.QtWidgets import (QApplication, QGraphicsScene, - QGraphicsView, QGraphicsSimpleTextItem, QGraphicsItem) +from PySide6.QtWidgets import (QApplication, QGraphicsScene, QGraphicsView, + QGraphicsSimpleTextItem, QGraphicsItem) from PySide6.QtCore import Qt, QPointF, QRectF, QRect from PySide6.QtCharts import QChart, QLineSeries, QSplineSeries from PySide6.QtGui import QPainter, QFont, QFontMetrics, QPainterPath, QColor @@ -42,37 +42,37 @@ class Callout(QGraphicsItem): # establish the position of the anchor point in relation to _rect above = anchor.y() <= self._rect.top() - above_center = (anchor.y() > self._rect.top() and - anchor.y() <= self._rect.center().y()) - below_center = (anchor.y() > self._rect.center().y() and - anchor.y() <= self._rect.bottom()) + above_center = (anchor.y() > self._rect.top() + and anchor.y() <= self._rect.center().y()) + below_center = (anchor.y() > self._rect.center().y() + and anchor.y() <= self._rect.bottom()) below = anchor.y() > self._rect.bottom() on_left = anchor.x() <= self._rect.left() - left_of_center = (anchor.x() > self._rect.left() and - anchor.x() <= self._rect.center().x()) - right_of_center = (anchor.x() > self._rect.center().x() and - anchor.x() <= self._rect.right()) + left_of_center = (anchor.x() > self._rect.left() + and anchor.x() <= self._rect.center().x()) + right_of_center = (anchor.x() > self._rect.center().x() + and anchor.x() <= self._rect.right()) on_right = anchor.x() > self._rect.right() # get the nearest _rect corner. x = (on_right + right_of_center) * self._rect.width() y = (below + below_center) * self._rect.height() - corner_case = ((above and on_left) or (above and on_right) or - (below and on_left) or (below and on_right)) + corner_case = ((above and on_left) or (above and on_right) + or (below and on_left) or (below and on_right)) vertical = abs(anchor.x() - x) > abs(anchor.y() - y) - x1 = (x + left_of_center * 10 - right_of_center * 20 + corner_case * - int(not vertical) * (on_left * 10 - on_right * 20)) - y1 = (y + above_center * 10 - below_center * 20 + corner_case * - vertical * (above * 10 - below * 20)) + x1 = (x + left_of_center * 10 - right_of_center * 20 + corner_case + * int(not vertical) * (on_left * 10 - on_right * 20)) + y1 = (y + above_center * 10 - below_center * 20 + corner_case + * vertical * (above * 10 - below * 20)) point1.setX(x1) point1.setY(y1) - x2 = (x + left_of_center * 20 - right_of_center * 10 + corner_case * - int(not vertical) * (on_left * 20 - on_right * 10)) - y2 = (y + above_center * 20 - below_center * 10 + corner_case * - vertical * (above * 20 - below * 10)) + x2 = (x + left_of_center * 20 - right_of_center * 10 + corner_case + * int(not vertical) * (on_left * 20 - on_right * 10)) + y2 = (y + above_center * 20 - below_center * 10 + corner_case + * vertical * (above * 20 - below * 10)) point2.setX(x2) point2.setY(y2) @@ -90,7 +90,7 @@ class Callout(QGraphicsItem): def mouseMoveEvent(self, event): if event.buttons() & Qt.LeftButton: - self.setPos(mapToParent( + self.setPos(self.mapToParent( event.pos() - event.buttonDownPos(Qt.LeftButton))) event.setAccepted(True) else: @@ -127,7 +127,7 @@ class View(QGraphicsView): self._chart = QChart() self._chart.setMinimumSize(640, 480) self._chart.setTitle("Hover the line to show callout. Click the line " - "to make it stay") + "to make it stay") self._chart.legend().hide() self.series = QLineSeries() self.series.append(1, 3) diff --git a/examples/charts/chartthemes/main.py b/examples/charts/chartthemes/main.py index 28c819c1..5787710c 100644 --- a/examples/charts/chartthemes/main.py +++ b/examples/charts/chartthemes/main.py @@ -7,7 +7,7 @@ import sys from PySide6.QtCore import QPointF, Qt from PySide6.QtGui import QColor, QPainter, QPalette from PySide6.QtWidgets import (QApplication, QMainWindow, QSizePolicy, - QWidget) + QWidget) from PySide6.QtCharts import (QAreaSeries, QBarSet, QChart, QChartView, QLineSeries, QPieSeries, QScatterSeries, QSplineSeries, QStackedBarSeries) @@ -27,7 +27,7 @@ class ThemeWidget(QWidget): self.value_max = 10 self.value_count = 7 self.data_table = self.generate_random_data(self.list_count, - self.value_max, self.value_count) + self.value_max, self.value_count) self.ui.setupUi(self) self.populate_themebox() @@ -41,8 +41,7 @@ class ThemeWidget(QWidget): # Pie Chart chart_view = QChartView(self.create_pie_chart()) - chart_view.setSizePolicy(QSizePolicy.Ignored, - QSizePolicy.Ignored) + chart_view.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Ignored) self.ui.gridLayout.addWidget(chart_view, 1, 1) self.charts.append(chart_view) @@ -70,10 +69,10 @@ class ThemeWidget(QWidget): self.ui.antialiasCheckBox.setChecked(True) # Set the colors from the light theme as default ones - pal = qApp.palette() + pal = qApp.palette() # noqa: F821 pal.setColor(QPalette.Window, QColor(0xf0f0f0)) pal.setColor(QPalette.WindowText, QColor(0x404044)) - qApp.setPalette(pal) + qApp.setPalette(pal) # noqa: F821 self.update_ui() @@ -273,7 +272,6 @@ class ThemeWidget(QWidget): for chart_view in self.charts: chart_view.chart().setTheme(theme) - # Set palette colors based on selected theme if theme == QChart.ChartThemeLight: set_colors(QColor(0xf0f0f0), QColor(0x404044)) diff --git a/examples/charts/donutbreakdown/donutbreakdown.py b/examples/charts/donutbreakdown/donutbreakdown.py index 1597685c..9111d4aa 100644 --- a/examples/charts/donutbreakdown/donutbreakdown.py +++ b/examples/charts/donutbreakdown/donutbreakdown.py @@ -38,7 +38,7 @@ class MainSlice(QPieSlice): class DonutBreakdownChart(QChart): def __init__(self, parent=None): super().__init__(QChart.ChartTypeCartesian, - parent, Qt.WindowFlags()) + parent, Qt.WindowFlags()) self.main_series = QPieSeries() self.main_series.setPieSize(0.7) self.addSeries(self.main_series) diff --git a/examples/charts/legend/legend.py b/examples/charts/legend/legend.py index f1929547..5417a940 100644 --- a/examples/charts/legend/legend.py +++ b/examples/charts/legend/legend.py @@ -7,7 +7,8 @@ import sys from PySide6.QtCore import Qt, QRectF, Slot from PySide6.QtGui import QBrush, QColor, QPainter, QPen from PySide6.QtWidgets import (QApplication, QDoubleSpinBox, - QFormLayout, QGridLayout, QGroupBox, QPushButton, QWidget) + QFormLayout, QGridLayout, QGroupBox, + QPushButton, QWidget) from PySide6.QtCharts import QBarSeries, QBarSet, QChart, QChartView @@ -206,10 +207,8 @@ class MainWidget(QWidget): def update_legend_layout(self): legend = self.chart.legend() - rect = QRectF(self.legend_posx.value(), - self.legend_posy.value(), - self.legend_width.value(), - self.legend_height.value()) + rect = QRectF(self.legend_posx.value(), self.legend_posy.value(), + self.legend_width.value(), self.legend_height.value()) legend.setGeometry(rect) legend.update() diff --git a/examples/charts/memoryusage/memoryusage.py b/examples/charts/memoryusage/memoryusage.py index d0bf842e..3ba42368 100644 --- a/examples/charts/memoryusage/memoryusage.py +++ b/examples/charts/memoryusage/memoryusage.py @@ -54,7 +54,7 @@ def get_memory_usage(): legend = f'{command} {memory_usage}%' result.append([legend, memory_usage]) - result.sort(key = lambda x: x[1], reverse=True) + result.sort(key=lambda x: x[1], reverse=True) return result diff --git a/examples/charts/modeldata/modeldata.py b/examples/charts/modeldata/modeldata.py index 64602103..0e36f777 100644 --- a/examples/charts/modeldata/modeldata.py +++ b/examples/charts/modeldata/modeldata.py @@ -9,7 +9,7 @@ from random import randrange from PySide6.QtCore import QAbstractTableModel, QModelIndex, QRect, Qt from PySide6.QtGui import QColor, QPainter from PySide6.QtWidgets import (QApplication, QGridLayout, QHeaderView, - QTableView, QWidget) + QTableView, QWidget) from PySide6.QtCharts import QChart, QChartView, QLineSeries, QVXYModelMapper @@ -101,9 +101,6 @@ class TableWidget(QWidget): self.mapper.setModel(self.model) self.chart.addSeries(self.series) - # for storing color hex from the series - seriesColorHex = "#000000" - # get the color of the series and use it for showing the mapped area self.model.add_mapping(self.series.pen().color().name(), QRect(0, 0, 2, self.model.rowCount())) diff --git a/examples/charts/percentbarchart/percentbarchart.py b/examples/charts/percentbarchart/percentbarchart.py index cfb11800..9f70c032 100644 --- a/examples/charts/percentbarchart/percentbarchart.py +++ b/examples/charts/percentbarchart/percentbarchart.py @@ -21,11 +21,11 @@ class MainWindow(QMainWindow): set3 = QBarSet("Mary") set4 = QBarSet("Samantha") - set0.append([1, 2, 3, 4, 5, 6]) - set1.append([5, 0, 0, 4, 0, 7]) + set0.append([1, 2, 3, 4, 5, 6]) + set1.append([5, 0, 0, 4, 0, 7]) set2.append([3, 5, 8, 13, 8, 5]) - set3.append([5, 6, 7, 3, 4, 5]) - set4.append([9, 7, 5, 3, 1, 2]) + set3.append([5, 6, 7, 3, 4, 5]) + set4.append([9, 7, 5, 3, 1, 2]) series = QPercentBarSeries() series.append(set0) diff --git a/examples/charts/pointconfiguration/chartwindow.py b/examples/charts/pointconfiguration/chartwindow.py index 055802b9..36b10aa1 100644 --- a/examples/charts/pointconfiguration/chartwindow.py +++ b/examples/charts/pointconfiguration/chartwindow.py @@ -109,14 +109,14 @@ class ChartWindow(QMainWindow): self._selectedPointConfig = self._series.pointConfiguration(index) selected_point = self._series.at(index) selected_index_lineedit = self._selected_point_index_lineedit - selected_index_lineedit.setText("(" + str(selected_point.x()) + ", " + - str(selected_point.y()) + ")") + selected_index_lineedit.setText("(" + str(selected_point.x()) + ", " + + str(selected_point.y()) + ")") config = self._series.pointConfiguration(index) color = config.get(PointConfig.Color) or self._series.color() size = config.get(PointConfig.Size) or self._series.markerSize() - labelVisibility = (config.get(PointConfig.LabelVisibility) or - self._series.pointLabelsVisible()) + labelVisibility = (config.get(PointConfig.LabelVisibility) + or self._series.pointLabelsVisible()) customLabel = config.get(PointConfig.LabelFormat) or "" combobox_value_list = [ diff --git a/examples/charts/pointselectionandmarkers/pointselectionandmarkers.py b/examples/charts/pointselectionandmarkers/pointselectionandmarkers.py index 4f9540d4..df7b6168 100644 --- a/examples/charts/pointselectionandmarkers/pointselectionandmarkers.py +++ b/examples/charts/pointselectionandmarkers/pointselectionandmarkers.py @@ -7,7 +7,8 @@ import sys from PySide6.QtCore import Slot, QPointF, Qt from PySide6.QtCharts import QChart, QChartView, QSplineSeries from PySide6.QtGui import QPainter, QImage -from PySide6.QtWidgets import QApplication, QMainWindow, QWidget, QGridLayout, QComboBox, QCheckBox, QLabel, QHBoxLayout +from PySide6.QtWidgets import (QApplication, QMainWindow, QWidget, QGridLayout, + QComboBox, QCheckBox, QLabel, QHBoxLayout) import utilities as Utilities @@ -20,12 +21,12 @@ if __name__ == "__main__": marker_size = 20. series = QSplineSeries() series.append([QPointF(0, 0), - QPointF(0.5, 2.27), - QPointF(1.5, 2.2), - QPointF(3.3, 1.7), - QPointF(4.23, 3.1), - QPointF(5.3, 2.3), - QPointF(6.47, 4.1)]) + QPointF(0.5, 2.27), + QPointF(1.5, 2.2), + QPointF(3.3, 1.7), + QPointF(4.23, 3.1), + QPointF(5.3, 2.3), + QPointF(6.47, 4.1)]) series.setMarkerSize(marker_size) series.setLightMarker(Utilities.default_light_marker(marker_size)) series.setSelectedLightMarker(Utilities.default_selected_light_marker(marker_size)) @@ -66,16 +67,16 @@ if __name__ == "__main__": char_point_combobox.addItems(["Red rectangle", "Green triangle", "Orange circle"]) char_point_combobox.currentIndexChanged.connect(set_light_marker) - @Slot(int) def set_selected_light_marker(index): - series.setSelectedLightMarker(Utilities.get_selected_point_representation(Utilities.selected_point_type(index), marker_size)) + series.setSelectedLightMarker( + Utilities.get_selected_point_representation( + Utilities.selected_point_type(index), marker_size)) char_point_selected = QLabel("Char point selected: ") char_point_selected_combobox.addItems(["Blue triangle", "Yellow rectangle", "Lavender circle"]) char_point_selected_combobox.currentIndexChanged.connect(set_selected_light_marker) - @Slot(int) def set_line_color(index): series.setColor(Utilities.make_line_color(Utilities.line_color(index))) @@ -84,11 +85,12 @@ if __name__ == "__main__": line_color_combobox.addItems(["Blue", "Black", "Mint"]) line_color_combobox.currentIndexChanged.connect(set_line_color) - @Slot(int) def display_unselected_points(checkbox_state): if checkbox_state: - series.setLightMarker(Utilities.get_point_representation(Utilities.point_type(char_point_combobox.currentIndex()), marker_size)) + series.setLightMarker( + Utilities.get_point_representation( + Utilities.point_type(char_point_combobox.currentIndex()), marker_size)) else: series.setLightMarker(QImage()) @@ -96,7 +98,6 @@ if __name__ == "__main__": show_unselected_points_checkbox.setChecked(True) show_unselected_points_checkbox.stateChanged.connect(display_unselected_points) - control_label = QLabel("Marker and Selection Controls") control_label.setAlignment(Qt.AlignHCenter) control_label_font = control_label.font() diff --git a/examples/charts/pointselectionandmarkers/utilities.py b/examples/charts/pointselectionandmarkers/utilities.py index 6b96d6e2..b27a2542 100644 --- a/examples/charts/pointselectionandmarkers/utilities.py +++ b/examples/charts/pointselectionandmarkers/utilities.py @@ -4,7 +4,8 @@ from PySide6.QtGui import QImage, QPainter, QColor from PySide6.QtCore import Qt -import rc_markers +import rc_markers # noqa: F401 + def rectangle(point_type, image_size): image = QImage(image_size, image_size, QImage.Format_RGB32) @@ -15,9 +16,11 @@ def rectangle(point_type, image_size): painter.end() return image + def triangle(point_type, image_size): return QImage(point_type[3]).scaled(image_size, image_size) + def circle(point_type, image_size): image = QImage(image_size, image_size, QImage.Format_ARGB32) image.fill(QColor(0, 0, 0, 0)) @@ -32,6 +35,7 @@ def circle(point_type, image_size): painter.end() return image + _point_types = [("RedRectangle", rectangle, Qt.red), ("GreenTriangle", triangle, Qt.green, ":/images/green_triangle.png"), ("OrangeCircle", circle, QColor(255, 127, 80))] @@ -40,12 +44,15 @@ _selected_point_types = [("BlueTriangle", triangle, Qt.blue, ":/images/blue_tria ("LavenderCircle", circle, QColor(147, 112, 219))] _line_colors = [("Blue", QColor(65, 105, 225)), ("Black", Qt.black), ("Mint", QColor(70, 203, 155))] + def point_type(index): return _point_types[index] + def selected_point_type(index): return _selected_point_types[index] + def line_color(index): return _line_colors[index] @@ -53,6 +60,7 @@ def line_color(index): def default_light_marker(image_size): return rectangle(_point_types[0], image_size) + def default_selected_light_marker(image_size): return triangle(_selected_point_types[0], image_size) @@ -60,8 +68,10 @@ def default_selected_light_marker(image_size): def get_point_representation(point_type, image_size): return point_type[1](point_type, image_size) + def get_selected_point_representation(point_type, image_size): return point_type[1](point_type, image_size) + def make_line_color(line_color): return line_color[1] diff --git a/examples/corelib/ipc/sharedmemory/dialog.py b/examples/corelib/ipc/sharedmemory/dialog.py index c374f011..13490004 100644 --- a/examples/corelib/ipc/sharedmemory/dialog.py +++ b/examples/corelib/ipc/sharedmemory/dialog.py @@ -90,4 +90,4 @@ class Dialog(QDialog): def detach(self): if not self._shared_memory.detach(): - self.ui.label.setText(tr("Unable to detach from shared memory.")) + self.ui.label.setText(self.tr("Unable to detach from shared memory.")) # noqa: F821 diff --git a/examples/corelib/settingseditor/settingseditor.py b/examples/corelib/settingseditor/settingseditor.py index a77349e5..f87a2f4b 100644 --- a/examples/corelib/settingseditor/settingseditor.py +++ b/examples/corelib/settingseditor/settingseditor.py @@ -7,14 +7,20 @@ import sys from PySide6.QtCore import (QByteArray, QDate, QDateTime, QDir, QEvent, QPoint, - QRect, QRegularExpression, QSettings, QSize, QTime, QTimer, Qt, Slot) + QRect, QRegularExpression, QSettings, QSize, QTime, + QTimer, Qt, Slot) from PySide6.QtGui import (QAction, QColor, QIcon, QIntValidator, - QDoubleValidator, QRegularExpressionValidator, QValidator) + QDoubleValidator, QRegularExpressionValidator, + QValidator) from PySide6.QtWidgets import (QAbstractItemView, QApplication, - QCheckBox, QComboBox, QFileDialog, QDialog, QDialogButtonBox, QGridLayout, - QGroupBox, QHeaderView, QInputDialog, QItemDelegate, QLabel, QLineEdit, - QMainWindow, QMessageBox, QStyle, QSpinBox, QStyleOptionViewItem, - QTableWidget, QTableWidgetItem, QTreeWidget, QTreeWidgetItem, QVBoxLayout) + QCheckBox, QComboBox, QFileDialog, QDialog, + QDialogButtonBox, QGridLayout, + QGroupBox, QHeaderView, QInputDialog, + QItemDelegate, QLabel, QLineEdit, + QMainWindow, QMessageBox, QStyle, QSpinBox, + QStyleOptionViewItem, QTableWidget, + QTableWidgetItem, QTreeWidget, QTreeWidgetItem, + QVBoxLayout) class TypeChecker: @@ -160,7 +166,7 @@ class MainWindow(QMainWindow): @Slot() def open_inifile(self): file_name, _ = QFileDialog.getOpenFileName(self, "Open INI File", - '', "INI Files (*.ini *.conf)") + '', "INI Files (*.ini *.conf)") if file_name: self.load_ini_file(file_name) @@ -175,7 +181,8 @@ class MainWindow(QMainWindow): @Slot() def open_property_list(self): file_name, _ = QFileDialog.getOpenFileName(self, - "Open Property List", '', "Property List Files (*.plist)") + "Open Property List", '', + "Property List Files (*.plist)") if file_name: settings = QSettings(file_name, QSettings.NativeFormat) @@ -185,8 +192,8 @@ class MainWindow(QMainWindow): @Slot() def open_registry_path(self): path, ok = QInputDialog.getText(self, "Open Registry Path", - "Enter the path in the Windows registry:", - QLineEdit.Normal, 'HKEY_CURRENT_USER\\') + "Enter the path in the Windows registry:", + QLineEdit.Normal, 'HKEY_CURRENT_USER\\') if ok and path != '': settings = QSettings(path, QSettings.NativeFormat) @@ -196,56 +203,46 @@ class MainWindow(QMainWindow): @Slot() def about(self): QMessageBox.about(self, "About Settings Editor", - "The Settings Editor example shows how to access " - "application settings using Qt.") - - def create_actions(self): - self._open_settings_act = QAction("&Open Application Settings...", - self, shortcut="Ctrl+O", triggered=self.openSettings) - - self._open_ini_file_act = QAction("Open I&NI File...", self, - shortcut="Ctrl+N", triggered=self.openIniFile) - - self._open_property_list_act = QAction("Open macOS &Property List...", - self, shortcut="Ctrl+P", triggered=self.openPropertyList) + "The Settings Editor example shows how to access " + "application settings using Qt.") def create_actions(self): self.open_settings_action = QAction("&Open Application Settings...", - self, shortcut="Ctrl+O", triggered=self.open_settings) + self, shortcut="Ctrl+O", triggered=self.open_settings) self.open_ini_file_action = QAction("Open I&NI File...", self, - shortcut="Ctrl+N", triggered=self.open_inifile) + shortcut="Ctrl+N", triggered=self.open_inifile) - self.open_property_list_action = QAction("Open macOS &Property List...", - self, shortcut="Ctrl+P", triggered=self.open_property_list) + self.open_property_list_action = QAction("Open macOS &Property List...", self, + shortcut="Ctrl+P", + triggered=self.open_property_list) if sys.platform != 'darwin': self.open_property_list_action.setEnabled(False) self.open_registry_path_action = QAction( - "Open Windows &Registry Path...", self, shortcut="Ctrl+G", - triggered=self.open_registry_path) + "Open Windows &Registry Path...", self, shortcut="Ctrl+G", + triggered=self.open_registry_path) if sys.platform != 'win32': self.open_registry_path_action.setEnabled(False) self.refresh_action = QAction("&Refresh", self, shortcut="Ctrl+R", - enabled=False, triggered=self.settings_tree.refresh) + enabled=False, triggered=self.settings_tree.refresh) - self.exit_action = QAction("E&xit", self, shortcut="Ctrl+Q", - triggered=self.close) + self.exit_action = QAction("E&xit", self, shortcut="Ctrl+Q", triggered=self.close) self.auto_refresh_action = QAction("&Auto-Refresh", self, - shortcut="Ctrl+A", checkable=True, enabled=False) + shortcut="Ctrl+A", checkable=True, enabled=False) self.auto_refresh_action.triggered[bool].connect(self.settings_tree.set_auto_refresh) self.auto_refresh_action.triggered[bool].connect(self.refresh_action.setDisabled) self.fallbacks_action = QAction("&Fallbacks", self, - shortcut="Ctrl+F", checkable=True, enabled=False) + shortcut="Ctrl+F", checkable=True, enabled=False) self.fallbacks_action.triggered[bool].connect(self.settings_tree.set_fallbacks_enabled) self.about_action = QAction("&About", self, triggered=self.about) self.about_Qt_action = QAction("About &Qt", self, - triggered=qApp.aboutQt) + triggered=qApp.aboutQt) # noqa: F821 def create_menus(self): self.file_menu = self.menuBar().addMenu("&File") @@ -508,7 +505,7 @@ class SettingsTree(QTreeWidget): # The signal might not be connected. try: self.itemChanged.disconnect(self.update_setting) - except: + except Exception: pass self.settings.sync() @@ -531,7 +528,6 @@ class SettingsTree(QTreeWidget): key = ancestor.text(0) + '/' + key ancestor = ancestor.parent() - d = item.data(2, Qt.UserRole) self.settings.setValue(key, item.data(2, Qt.UserRole)) if self.auto_refresh: @@ -703,7 +699,7 @@ class VariantDelegate(QItemDelegate): value = editor.value() else: value = self.value_from_lineedit(editor, model, index) - if not value is None: + if value is not None: model.setData(index, value, Qt.UserRole) model.setData(index, self.display_text(value), Qt.DisplayRole) diff --git a/examples/corelib/threads/mandelbrot.py b/examples/corelib/threads/mandelbrot.py index b8cab06e..4689813d 100644 --- a/examples/corelib/threads/mandelbrot.py +++ b/examples/corelib/threads/mandelbrot.py @@ -30,7 +30,7 @@ INFO_KEY = 'info' HELP = ("Use mouse wheel or the '+' and '-' keys to zoom. Press and " - "hold left mouse button to scroll.") + "hold left mouse button to scroll.") class RenderThread(QThread): @@ -53,7 +53,8 @@ class RenderThread(QThread): self.abort = False for i in range(RenderThread.colormap_size): - self.colormap.append(self.rgb_from_wave_length(380.0 + (i * 400.0 / RenderThread.colormap_size))) + self.colormap.append( + self.rgb_from_wave_length(380.0 + (i * 400.0 / RenderThread.colormap_size))) def stop(self): self.mutex.lock() @@ -132,7 +133,8 @@ class RenderThread(QThread): if num_iterations < max_iterations: image.setPixel(x + half_width, y + half_height, - self.colormap[num_iterations % RenderThread.colormap_size]) + self.colormap[ + num_iterations % RenderThread.colormap_size]) all_black = False else: image.setPixel(x + half_width, y + half_height, qRgb(0, 0, 0)) @@ -221,7 +223,7 @@ class MandelbrotWidget(QWidget): if self.pixmap.isNull(): painter.setPen(Qt.white) painter.drawText(self.rect(), Qt.AlignCenter, - "Rendering initial image, please wait...") + "Rendering initial image, please wait...") return if self._cur_scale == self._pixmap_scale: @@ -250,10 +252,10 @@ class MandelbrotWidget(QWidget): painter.setPen(Qt.NoPen) painter.setBrush(QColor(0, 0, 0, 127)) painter.drawRect((self.width() - text_width) / 2 - 5, 0, text_width + 10, - metrics.lineSpacing() + 5) + metrics.lineSpacing() + 5) painter.setPen(Qt.white) painter.drawText((self.width() - text_width) / 2, - metrics.leading() + metrics.ascent(), text) + metrics.leading() + metrics.ascent(), text) def resizeEvent(self, event): self.thread.render(self._center_x, self._center_y, self._cur_scale, self.size()) @@ -302,7 +304,7 @@ class MandelbrotWidget(QWidget): delta_y = (self.height() - self.pixmap.height()) / 2 - self._pixmap_offset.y() self.scroll(delta_x, delta_y) - @Slot(QImage,float) + @Slot(QImage, float) def update_pixmap(self, image, scale_factor): if not self._last_drag_pos.isNull(): return @@ -317,15 +319,13 @@ class MandelbrotWidget(QWidget): def zoom(self, zoomFactor): self._cur_scale *= zoomFactor self.update() - self.thread.render(self._center_x, self._center_y, self._cur_scale, - self.size()) + self.thread.render(self._center_x, self._center_y, self._cur_scale, self.size()) def scroll(self, deltaX, deltaY): self._center_x += deltaX * self._cur_scale self._center_y += deltaY * self._cur_scale self.update() - self.thread.render(self._center_x, self._center_y, self._cur_scale, - self.size()) + self.thread.render(self._center_x, self._center_y, self._cur_scale, self.size()) if __name__ == '__main__': diff --git a/examples/datavisualization/graphgallery/bargraph.py b/examples/datavisualization/graphgallery/bargraph.py index 6c61d670..7938a5ca 100644 --- a/examples/datavisualization/graphgallery/bargraph.py +++ b/examples/datavisualization/graphgallery/bargraph.py @@ -8,8 +8,7 @@ from PySide6.QtGui import QFont from PySide6.QtWidgets import (QButtonGroup, QCheckBox, QComboBox, QFontComboBox, QLabel, QPushButton, QHBoxLayout, QSizePolicy, QRadioButton, QSlider, QVBoxLayout, QWidget) -from PySide6.QtDataVisualization import (QAbstract3DGraph, QAbstract3DSeries, - Q3DBars) +from PySide6.QtDataVisualization import (QAbstract3DGraph, QAbstract3DSeries, Q3DBars) class BarGraph(QObject): @@ -92,18 +91,14 @@ class BarGraph(QObject): selectionModeList.addItem("Slice into Row and Item", sel) sel = QAbstract3DGraph.SelectionSlice | QAbstract3DGraph.SelectionColumn selectionModeList.addItem("Slice into Column", sel) - sel = (QAbstract3DGraph.SelectionSlice - | QAbstract3DGraph.SelectionItemAndColumn) + sel = (QAbstract3DGraph.SelectionSlice | QAbstract3DGraph.SelectionItemAndColumn) selectionModeList.addItem("Slice into Column and Item", sel) - sel = (QAbstract3DGraph.SelectionItemRowAndColumn - | QAbstract3DGraph.SelectionMultiSeries) + sel = (QAbstract3DGraph.SelectionItemRowAndColumn | QAbstract3DGraph.SelectionMultiSeries) selectionModeList.addItem("Multi: Bar, Row, Col", sel) - sel = (QAbstract3DGraph.SelectionSlice - | QAbstract3DGraph.SelectionItemAndRow + sel = (QAbstract3DGraph.SelectionSlice | QAbstract3DGraph.SelectionItemAndRow | QAbstract3DGraph.SelectionMultiSeries) selectionModeList.addItem("Multi, Slice: Row, Item", sel) - sel = (QAbstract3DGraph.SelectionSlice - | QAbstract3DGraph.SelectionItemAndColumn + sel = (QAbstract3DGraph.SelectionSlice | QAbstract3DGraph.SelectionItemAndColumn | QAbstract3DGraph.SelectionMultiSeries) selectionModeList.addItem("Multi, Slice: Col, Item", sel) selectionModeList.setCurrentIndex(1) diff --git a/examples/datavisualization/graphgallery/main.py b/examples/datavisualization/graphgallery/main.py index 66811724..4b57b85d 100644 --- a/examples/datavisualization/graphgallery/main.py +++ b/examples/datavisualization/graphgallery/main.py @@ -34,8 +34,8 @@ if __name__ == "__main__": surface = SurfaceGraph() if (not bars.initialize(minimum_graph_size, screen_size) - or not scatter.initialize(minimum_graph_size, screen_size) - or not surface.initialize(minimum_graph_size, screen_size)): + or not scatter.initialize(minimum_graph_size, screen_size) + or not surface.initialize(minimum_graph_size, screen_size)): QMessageBox.warning(None, "Graph Gallery", "Couldn't initialize the OpenGL context.") sys.exit(-1) diff --git a/examples/datavisualization/graphgallery/variantbardataproxy.py b/examples/datavisualization/graphgallery/variantbardataproxy.py index 2b6f5d84..f69ebaf8 100644 --- a/examples/datavisualization/graphgallery/variantbardataproxy.py +++ b/examples/datavisualization/graphgallery/variantbardataproxy.py @@ -64,8 +64,8 @@ class VariantBarDataProxy(QBarDataProxy): # If we have no data or mapping, or the categories are not defined, # simply clear the array if (not self._dataSet or not self._mapping - or not self._mapping.rowCategories() - or not self._mapping.columnCategories()): + or not self._mapping.rowCategories() + or not self._mapping.columnCategories()): self.resetArray() return diff --git a/examples/datavisualization/qmlsurfacegallery/main.py b/examples/datavisualization/qmlsurfacegallery/main.py index a02e060c..7b980bc1 100644 --- a/examples/datavisualization/qmlsurfacegallery/main.py +++ b/examples/datavisualization/qmlsurfacegallery/main.py @@ -12,8 +12,8 @@ from PySide6.QtGui import QGuiApplication from PySide6.QtQuick import QQuickView from PySide6.QtDataVisualization import qDefaultSurfaceFormat -from datasource import DataSource -import rc_qmlsurfacegallery +from datasource import DataSource # noqa: F401 +import rc_qmlsurfacegallery # noqa: F401 if __name__ == "__main__": diff --git a/examples/dbus/pingpong/ping.py b/examples/dbus/pingpong/ping.py index 8dd741c0..d61f2549 100644 --- a/examples/dbus/pingpong/ping.py +++ b/examples/dbus/pingpong/ping.py @@ -5,7 +5,7 @@ import sys from PySide6.QtCore import QCoreApplication -from PySide6.QtDBus import QDBusConnection, QDBusInterface, QDBusReply +from PySide6.QtDBus import QDBusConnection, QDBusInterface, QDBusReply SERVICE_NAME = 'org.example.QtDBus.PingExample' @@ -36,4 +36,3 @@ if __name__ == "__main__": value = reply.value() print(f'ping: Reply was: {value}') sys.exit(0) - diff --git a/examples/dbus/pingpong/pong.py b/examples/dbus/pingpong/pong.py index cdb610c5..0dec6eda 100644 --- a/examples/dbus/pingpong/pong.py +++ b/examples/dbus/pingpong/pong.py @@ -18,7 +18,7 @@ class Pong(QObject): @Slot(str, result=str) def ping(self, arg): print(f'pong: Received ping({arg})') - qApp.quit() + qApp.quit() # noqa: F821 return f'ping("{arg}") got called' diff --git a/examples/demos/documentviewer/mainwindow.py b/examples/demos/documentviewer/mainwindow.py index 99202bf7..e9abd0be 100644 --- a/examples/demos/documentviewer/mainwindow.py +++ b/examples/demos/documentviewer/mainwindow.py @@ -1,8 +1,7 @@ # Copyright (C) 2023 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause -from PySide6.QtWidgets import (QDialog, QFileDialog, QMainWindow, - QMessageBox, QToolButton) +from PySide6.QtWidgets import (QDialog, QFileDialog, QMainWindow, QMessageBox) from PySide6.QtCore import (QDir, QFile, QFileInfo, QSettings, Slot) from ui_mainwindow import Ui_MainWindow diff --git a/examples/demos/documentviewer/pdfviewer/pdfviewer.py b/examples/demos/documentviewer/pdfviewer/pdfviewer.py index 0762cfdc..63d25bf7 100644 --- a/examples/demos/documentviewer/pdfviewer/pdfviewer.py +++ b/examples/demos/documentviewer/pdfviewer/pdfviewer.py @@ -48,7 +48,7 @@ class PdfViewer(AbstractViewer): nav = self._pdfView.pageNavigator() self._pageSelector = QPdfPageSelector(self._toolBar) self._toolBar.insertWidget(self._uiAssets_forward, self._pageSelector) - self._pageSelector.setDocument(self._document); + self._pageSelector.setDocument(self._document) self._pageSelector.currentPageChanged.connect(self.pageSelected) nav.currentPageChanged.connect(self._pageSelector.setCurrentPage) nav.backAvailableChanged.connect(self._uiAssets_back.setEnabled) diff --git a/examples/demos/documentviewer/recentfiles.py b/examples/demos/documentviewer/recentfiles.py index 7bcb4c0e..fdfc56d4 100644 --- a/examples/demos/documentviewer/recentfiles.py +++ b/examples/demos/documentviewer/recentfiles.py @@ -3,7 +3,7 @@ from enum import Enum, auto -from PySide6.QtCore import QFile, QFileInfo, QObject, QSettings, Signal, Slot +from PySide6.QtCore import QFileInfo, QObject, QSettings, Signal, Slot DEFAULT_MAX_FILES = 10 diff --git a/examples/demos/documentviewer/viewerfactory.py b/examples/demos/documentviewer/viewerfactory.py index 32a916be..ecae6770 100644 --- a/examples/demos/documentviewer/viewerfactory.py +++ b/examples/demos/documentviewer/viewerfactory.py @@ -65,7 +65,7 @@ class ViewerFactory: list = [] for name, viewer in self._viewers.items(): if ((self._defaultViewer and viewer.isDefaultViewer()) - or (not self._defaultViewer and name == "TxtViewer")): + or (not self._defaultViewer and name == "TxtViewer")): name += "(default)" list.append(name) return list diff --git a/examples/designer/taskmenuextension/registertictactoe.py b/examples/designer/taskmenuextension/registertictactoe.py index 4f163e37..2b014c79 100644 --- a/examples/designer/taskmenuextension/registertictactoe.py +++ b/examples/designer/taskmenuextension/registertictactoe.py @@ -1,7 +1,7 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause -from tictactoe import TicTacToe +from tictactoe import TicTacToe # noqa: F401 from tictactoeplugin import TicTacToePlugin from PySide6.QtDesigner import QPyDesignerCustomWidgetCollection diff --git a/examples/designer/taskmenuextension/tictactoe.py b/examples/designer/taskmenuextension/tictactoe.py index 9155d230..aa1c3158 100644 --- a/examples/designer/taskmenuextension/tictactoe.py +++ b/examples/designer/taskmenuextension/tictactoe.py @@ -89,27 +89,27 @@ class TicTacToe(QWidget): for position in range(0, 8, 3): if (self._state[position] != EMPTY - and self._state[position + 1] == self._state[position] - and self._state[position + 2] == self._state[position]): + and self._state[position + 1] == self._state[position] + and self._state[position + 2] == self._state[position]): y = self._cell_rect(position).center().y() painter.drawLine(0, y, self.width(), y) self._turn_number = 9 for position in range(3): if (self._state[position] != EMPTY - and self._state[position + 3] == self._state[position] - and self._state[position + 6] == self._state[position]): + and self._state[position + 3] == self._state[position] + and self._state[position + 6] == self._state[position]): x = self._cell_rect(position).center().x() painter.drawLine(x, 0, x, self.height()) self._turn_number = 9 if (self._state[0] != EMPTY and self._state[4] == self._state[0] - and self._state[8] == self._state[0]): + and self._state[8] == self._state[0]): painter.drawLine(0, 0, self.width(), self.height()) self._turn_number = 9 if (self._state[2] != EMPTY and self._state[4] == self._state[2] - and self._state[6] == self._state[2]): + and self._state[6] == self._state[2]): painter.drawLine(0, self.height(), self.width(), 0) self._turn_number = 9 diff --git a/examples/designer/taskmenuextension/tictactoeplugin.py b/examples/designer/taskmenuextension/tictactoeplugin.py index 0d538443..f9c92513 100644 --- a/examples/designer/taskmenuextension/tictactoeplugin.py +++ b/examples/designer/taskmenuextension/tictactoeplugin.py @@ -5,7 +5,7 @@ from tictactoe import TicTacToe from tictactoetaskmenu import TicTacToeTaskMenuFactory from PySide6.QtGui import QIcon -from PySide6.QtDesigner import QDesignerCustomWidgetInterface +from PySide6.QtDesigner import QDesignerCustomWidgetInterface DOM_XML = """ diff --git a/examples/external/matplotlib/widget3d/widget3d.py b/examples/external/matplotlib/widget3d/widget3d.py index bed6c7ef..8bfcc4ca 100644 --- a/examples/external/matplotlib/widget3d/widget3d.py +++ b/examples/external/matplotlib/widget3d/widget3d.py @@ -32,12 +32,12 @@ class ApplicationWindow(QMainWindow): # Main menu bar self.menu = self.menuBar() self.menu_file = self.menu.addMenu("File") - exit = QAction("Exit", self, triggered=qApp.quit) + exit = QAction("Exit", self, triggered=qApp.quit) # noqa: F821 self.menu_file.addAction(exit) self.menu_about = self.menu.addMenu("&About") about = QAction("About Qt", self, shortcut=QKeySequence(QKeySequence.HelpContents), - triggered=qApp.aboutQt) + triggered=qApp.aboutQt) # noqa: F821 self.menu_about.addAction(about) # Figure (Left) diff --git a/examples/external/opencv/webcam_pattern_detection.py b/examples/external/opencv/webcam_pattern_detection.py index a3a1e378..0c55a133 100644 --- a/examples/external/opencv/webcam_pattern_detection.py +++ b/examples/external/opencv/webcam_pattern_detection.py @@ -74,12 +74,12 @@ class Window(QMainWindow): # Main menu bar self.menu = self.menuBar() self.menu_file = self.menu.addMenu("File") - exit = QAction("Exit", self, triggered=qApp.quit) + exit = QAction("Exit", self, triggered=qApp.quit) # noqa: F821 self.menu_file.addAction(exit) self.menu_about = self.menu.addMenu("&About") about = QAction("About Qt", self, shortcut=QKeySequence(QKeySequence.HelpContents), - triggered=qApp.aboutQt) + triggered=qApp.aboutQt) # noqa: F821 self.menu_about.addAction(about) # Create a label for the display camera diff --git a/examples/external/scikit/staining_colors_separation.py b/examples/external/scikit/staining_colors_separation.py index b0aa143e..94fdc3bd 100644 --- a/examples/external/scikit/staining_colors_separation.py +++ b/examples/external/scikit/staining_colors_separation.py @@ -32,12 +32,12 @@ class ApplicationWindow(QMainWindow): # Main menu bar self.menu = self.menuBar() self.menu_file = self.menu.addMenu("File") - exit = QAction("Exit", self, triggered=qApp.quit) + exit = QAction("Exit", self, triggered=qApp.quit) # noqa: F821 self.menu_file.addAction(exit) self.menu_about = self.menu.addMenu("&About") about = QAction("About Qt", self, shortcut=QKeySequence(QKeySequence.HelpContents), - triggered=qApp.aboutQt) + triggered=qApp.aboutQt) # noqa: F821 self.menu_about.addAction(about) # Create an artificial color close to the original one diff --git a/examples/graphs/widgetgallery/main.py b/examples/graphs/widgetgallery/main.py index 4f773e3c..7bb2238a 100644 --- a/examples/graphs/widgetgallery/main.py +++ b/examples/graphs/widgetgallery/main.py @@ -3,7 +3,6 @@ """PySide6 port of the Qt Graphs widgetgallery example from Qt v6.x""" -import os import sys from PySide6.QtCore import QSize diff --git a/examples/graphs/widgetgallery/variantbardataproxy.py b/examples/graphs/widgetgallery/variantbardataproxy.py index 743dafcc..5ab2a2cd 100644 --- a/examples/graphs/widgetgallery/variantbardataproxy.py +++ b/examples/graphs/widgetgallery/variantbardataproxy.py @@ -64,8 +64,8 @@ class VariantBarDataProxy(QBarDataProxy): # If we have no data or mapping, or the categories are not defined, # simply clear the array if (not self._dataSet or not self._mapping - or not self._mapping.rowCategories() - or not self._mapping.columnCategories()): + or not self._mapping.rowCategories() + or not self._mapping.columnCategories()): self.resetArray() return diff --git a/examples/gui/analogclock/main.py b/examples/gui/analogclock/main.py index 7a5064a6..c0e00dd9 100644 --- a/examples/gui/analogclock/main.py +++ b/examples/gui/analogclock/main.py @@ -4,8 +4,7 @@ import sys from PySide6.QtCore import QPoint, QTimer, QTime, Qt -from PySide6.QtGui import (QColor, QGradient, QGuiApplication, QPainter, - QPalette, QPolygon, QRasterWindow) +from PySide6.QtGui import QGuiApplication, QPainter, QPalette, QPolygon, QRasterWindow """Simplified PySide6 port of the gui/analogclock example from Qt v6.x""" @@ -28,7 +27,7 @@ class AnalogClockWindow(QRasterWindow): self._seconds_hand = QPolygon([QPoint(1, 14), QPoint(-1, 14), QPoint(-1, -89), QPoint(1, -89)]) - palette = qApp.palette() + palette = qApp.palette() # noqa: F821 self._background_color = palette.color(QPalette.Window) self._hour_color = palette.color(QPalette.Text) self._minute_color = palette.color(QPalette.Text) @@ -59,7 +58,7 @@ class AnalogClockWindow(QRasterWindow): painter.drawConvexPolygon(self._hour_hand) painter.restore() - for i in range(0, 12): + for _ in range(0, 12): painter.drawRect(73, -3, 16, 6) painter.rotate(30.0) @@ -81,7 +80,7 @@ class AnalogClockWindow(QRasterWindow): painter.setPen(self._minute_color) - for j in range(0, 60): + for _ in range(0, 60): painter.drawLine(92, 0, 96, 0) painter.rotate(6.0) diff --git a/examples/gui/rhiwindow/main.py b/examples/gui/rhiwindow/main.py index 261adf38..cfb73b10 100644 --- a/examples/gui/rhiwindow/main.py +++ b/examples/gui/rhiwindow/main.py @@ -8,7 +8,7 @@ from PySide6.QtCore import QCoreApplication from PySide6.QtGui import QGuiApplication, QRhi, QSurfaceFormat from rhiwindow import HelloWindow -import rc_rhiwindow +import rc_rhiwindow # noqa: F401 if __name__ == "__main__": app = QGuiApplication(sys.argv) diff --git a/examples/gui/rhiwindow/rhiwindow.py b/examples/gui/rhiwindow/rhiwindow.py index dff56fec..fe054af4 100644 --- a/examples/gui/rhiwindow/rhiwindow.py +++ b/examples/gui/rhiwindow/rhiwindow.py @@ -5,11 +5,10 @@ import numpy import sys from PySide6.QtCore import (QEvent, QFile, QIODevice, QPointF, QRectF, QSize, - QSizeF, qFatal, qWarning, Qt) + qFatal, qWarning, Qt) from PySide6.QtGui import (QColor, QFont, QGradient, QImage, QMatrix4x4, - QOffscreenSurface, QPainter, QPlatformSurfaceEvent, - QSurface, QWindow) -from PySide6.QtGui import (QRhi, QRhiBuffer, QRhiCommandBuffer, + QPainter, QPlatformSurfaceEvent, QSurface, QWindow) +from PySide6.QtGui import (QRhi, QRhiBuffer, QRhiDepthStencilClearValue, QRhiGraphicsPipeline, QRhiNullInitParams, QRhiGles2InitParams, QRhiRenderBuffer, @@ -28,9 +27,9 @@ elif sys.platform == "darwin": # Y up (note clipSpaceCorrMatrix in m_viewProjection), CCW VERTEX_DATA = numpy.array([ - 0.0, 0.5, 1.0, 0.0, 0.0, - -0.5, -0.5, 0.0, 1.0, 0.0, - 0.5, -0.5, 0.0, 0.0, 1.0], dtype=numpy.float32) + 0.0, 0.5, 1.0, 0.0, 0.0, + -0.5, -0.5, 0.0, 1.0, 0.0, + 0.5, -0.5, 0.0, 0.0, 1.0], dtype=numpy.float32) UBUF_SIZE = 68 @@ -124,7 +123,8 @@ class RhiWindow(QWindow): surfaceSize = self.m_sc.surfacePixelSize() if self.m_hasSwapChain else QSize() # stop pushing frames when not exposed (or size is 0) - if (not is_exposed or (self.m_hasSwapChain and surfaceSize.isEmpty())) and self.m_initialized and not self.m_notExposed: + if ((not is_exposed or (self.m_hasSwapChain and surfaceSize.isEmpty())) + and self.m_initialized and not self.m_notExposed): self.m_notExposed = True # Continue when exposed again and the surface has a valid size. Note diff --git a/examples/installer_test/hello.py b/examples/installer_test/hello.py index a9819477..3921b5d5 100644 --- a/examples/installer_test/hello.py +++ b/examples/installer_test/hello.py @@ -31,7 +31,7 @@ class MyWidget(QWidget): super().__init__() self.hello = ["Hallo Welt", "你好,世界", "Hei maailma", - "Hola Mundo", "Привет мир"] + "Hola Mundo", "Привет мир"] self.button = QPushButton("Click me!") self.text = QLabel(f"Hello World auto_quit={auto_quit}") diff --git a/examples/location/mapviewer/doc/mapviewer.rst b/examples/location/mapviewer/doc/mapviewer.rst index d3054499..418e2e77 100644 --- a/examples/location/mapviewer/doc/mapviewer.rst +++ b/examples/location/mapviewer/doc/mapviewer.rst @@ -1,6 +1,8 @@ Map Viewer Example ================== +.. tags:: Android + The Map Viewer example shows how to display and interact with a map, search for an address, and find driving directions. diff --git a/examples/multimedia/audiooutput/audiooutput.py b/examples/multimedia/audiooutput/audiooutput.py index e0634e75..5be9b0d6 100644 --- a/examples/multimedia/audiooutput/audiooutput.py +++ b/examples/multimedia/audiooutput/audiooutput.py @@ -40,24 +40,23 @@ class Generator(QIODevice): sample_size = fmt.bytesPerSample() * 8 if sample_size == 8: if fmt.sampleFormat() == QAudioFormat.UInt8: - scaler = lambda x: ((1.0 + x) / 2 * 255) + scaler = lambda x: ((1.0 + x) / 2 * 255) # noqa: E731 pack_format = 'B' elif fmt.sampleFormat() == QAudioFormat.Int16: - scaler = lambda x: x * 127 + scaler = lambda x: x * 127 # noqa: E731 pack_format = 'b' elif sample_size == 16: little_endian = QSysInfo.ByteOrder == QSysInfo.LittleEndian if fmt.sampleFormat() == QAudioFormat.UInt8: - scaler = lambda x: (1.0 + x) / 2 * 65535 + scaler = lambda x: (1.0 + x) / 2 * 65535 # noqa: E731 pack_format = 'H' elif fmt.sampleFormat() == QAudioFormat.Int16: - scaler = lambda x: x * 32767 + scaler = lambda x: x * 32767 # noqa: E731 pack_format = 'h' - assert(pack_format != '') + assert pack_format != '' channel_bytes = fmt.bytesPerSample() - sample_bytes = fmt.channelCount() * channel_bytes length = (fmt.sampleRate() * fmt.channelCount() * channel_bytes) * durationUs // 100000 @@ -133,16 +132,14 @@ class AudioTest(QMainWindow): layout.addWidget(self.m_modeButton) - self.m_suspendResumeButton = QPushButton( - clicked=self.toggle_suspend_resume) + self.m_suspendResumeButton = QPushButton(clicked=self.toggle_suspend_resume) self.m_suspendResumeButton.setText(self.SUSPEND_LABEL) layout.addWidget(self.m_suspendResumeButton) volume_box = QHBoxLayout() volume_label = QLabel("Volume:") - self.m_volumeSlider = QSlider(Qt.Horizontal, minimum=0, maximum=100, - singleStep=10) + self.m_volumeSlider = QSlider(Qt.Horizontal, minimum=0, maximum=100, singleStep=10) self.m_volumeSlider.valueChanged.connect(self.volume_changed) volume_box.addWidget(volume_label) @@ -167,8 +164,8 @@ class AudioTest(QMainWindow): qWarning("Default format not supported - trying to use nearest") self.m_format = info.nearestFormat(self.m_format) - self.m_generator = Generator(self.m_format, - self.DURATION_SECONDS * 1000000, self.TONE_SAMPLE_RATE_HZ, self) + self.m_generator = Generator(self.m_format, self.DURATION_SECONDS * 1000000, + self.TONE_SAMPLE_RATE_HZ, self) self.create_audio_output() diff --git a/examples/multimedia/audiooutput/doc/audiooutput.rst b/examples/multimedia/audiooutput/doc/audiooutput.rst index bbd71c5d..fac7e33e 100644 --- a/examples/multimedia/audiooutput/doc/audiooutput.rst +++ b/examples/multimedia/audiooutput/doc/audiooutput.rst @@ -1,6 +1,8 @@ Audio Output Example ==================== +.. tags:: Android + Audio Output demonstrates the basic use cases of QAudioOutput. This example provides a tone generator to supply continuous audio playback. The diff --git a/examples/multimedia/audiosource/audiosource.py b/examples/multimedia/audiosource/audiosource.py index 555e0b93..bc2d606c 100644 --- a/examples/multimedia/audiosource/audiosource.py +++ b/examples/multimedia/audiosource/audiosource.py @@ -19,27 +19,15 @@ from typing import Optional import PySide6 from PySide6.QtCore import QByteArray, QMargins, Qt, Slot, qWarning from PySide6.QtGui import QPainter, QPalette -from PySide6.QtMultimedia import ( - QAudio, - QAudioDevice, - QAudioFormat, - QAudioSource, - QMediaDevices, -) -from PySide6.QtWidgets import ( - QApplication, - QComboBox, - QPushButton, - QSlider, - QVBoxLayout, - QWidget, - QLabel -) +from PySide6.QtMultimedia import QAudio, QAudioDevice, QAudioFormat, QAudioSource, QMediaDevices +from PySide6.QtWidgets import (QApplication, QComboBox, QPushButton, QSlider, QVBoxLayout, + QWidget, QLabel) is_android = os.environ.get('ANDROID_ARGUMENT') if is_android: - from PySide6.QtCore import QCoreApplication, QMicrophonePermission + from PySide6.QtCore import QMicrophonePermission + class AudioInfo: def __init__(self, format: QAudioFormat): @@ -107,9 +95,9 @@ class InputTest(QWidget): def initialize(self): if is_android: permission = QMicrophonePermission() - permission_status = qApp.checkPermission(permission) + permission_status = qApp.checkPermission(permission) # noqa: F821 if permission_status == Qt.PermissionStatus.Undetermined: - qApp.requestPermission(permission, self, self.initialize) + qApp.requestPermission(permission, self, self.initialize) # noqa: F821 return if permission_status == Qt.PermissionStatus.Denied: qWarning("Microphone permission is not granted!") diff --git a/examples/multimedia/audiosource/doc/audiosource.rst b/examples/multimedia/audiosource/doc/audiosource.rst index b1dcd61a..3a247c50 100644 --- a/examples/multimedia/audiosource/doc/audiosource.rst +++ b/examples/multimedia/audiosource/doc/audiosource.rst @@ -1,6 +1,8 @@ Audio Source Example ==================== +.. tags:: Android + A Python application that demonstrates the analogous example in C++ `Audio Source Example `_ diff --git a/examples/multimedia/camera/camera.py b/examples/multimedia/camera/camera.py index c1ea92fa..f66f28a2 100644 --- a/examples/multimedia/camera/camera.py +++ b/examples/multimedia/camera/camera.py @@ -17,7 +17,7 @@ from imagesettings import ImageSettings from videosettings import VideoSettings, is_android if is_android: - from PySide6.QtCore import QCoreApplication, QMicrophonePermission, QCameraPermission + from PySide6.QtCore import QMicrophonePermission, QCameraPermission from ui_camera_mobile import Ui_Camera else: from ui_camera import Ui_Camera @@ -45,7 +45,7 @@ class Camera(QMainWindow): image = Path(__file__).parent / "shutter.svg" self._ui.takeImageButton.setIcon(QIcon(os.fspath(image))) if not is_android: - self._ui.actionAbout_Qt.triggered.connect(qApp.aboutQt) + self._ui.actionAbout_Qt.triggered.connect(qApp.aboutQt) # noqa: F821 # disable all buttons by default self.updateCameraActive(False) @@ -63,9 +63,9 @@ class Camera(QMainWindow): if is_android: # camera cam_permission = QCameraPermission() - cam_permission_status = qApp.checkPermission(cam_permission) + cam_permission_status = qApp.checkPermission(cam_permission) # noqa: F821 if cam_permission_status == Qt.PermissionStatus.Undetermined: - qApp.requestPermission(cam_permission, self, self.initialize) + qApp.requestPermission(cam_permission, self, self.initialize) # noqa: F821 return if cam_permission_status == Qt.PermissionStatus.Denied: qWarning("Camera permission is not granted!") @@ -75,9 +75,9 @@ class Camera(QMainWindow): # microphone microphone_permission = QMicrophonePermission() - microphone_permission_status = qApp.checkPermission(microphone_permission) + microphone_permission_status = qApp.checkPermission(microphone_permission) # noqa: F821 if microphone_permission_status == Qt.PermissionStatus.Undetermined: - qApp.requestPermission(microphone_permission, self, self.initialize) + qApp.requestPermission(microphone_permission, self, self.initialize) # noqa: F821 return if microphone_permission_status == Qt.PermissionStatus.Denied: qWarning("Microphone permission is not granted!") diff --git a/examples/multimedia/camera/doc/camera.rst b/examples/multimedia/camera/doc/camera.rst index 8ef9f670..7fc75a38 100644 --- a/examples/multimedia/camera/doc/camera.rst +++ b/examples/multimedia/camera/doc/camera.rst @@ -1,6 +1,8 @@ Camera Example =============== +.. tags:: Android + The Camera Example shows how to use the API to capture a still image or video. The Camera Example demonstrates how you can use Qt Multimedia to implement some diff --git a/examples/multimedia/camera/videosettings.py b/examples/multimedia/camera/videosettings.py index 2359bd55..a88cb39e 100644 --- a/examples/multimedia/camera/videosettings.py +++ b/examples/multimedia/camera/videosettings.py @@ -14,7 +14,6 @@ else: from ui_videosettings import Ui_VideoSettingsUi - def box_value(box): idx = box.currentIndex() return None if idx == -1 else box.itemData(idx) diff --git a/examples/multimedia/player/player.py b/examples/multimedia/player/player.py index 88be50cc..4731386a 100644 --- a/examples/multimedia/player/player.py +++ b/examples/multimedia/player/player.py @@ -7,7 +7,7 @@ import sys from PySide6.QtCore import QStandardPaths, Qt, Slot from PySide6.QtGui import QAction, QIcon, QKeySequence from PySide6.QtWidgets import (QApplication, QDialog, QFileDialog, - QMainWindow, QSlider, QStyle, QToolBar) + QMainWindow, QSlider, QStyle, QToolBar) from PySide6.QtMultimedia import (QAudioOutput, QMediaFormat, QMediaPlayer) from PySide6.QtMultimediaWidgets import QVideoWidget @@ -100,7 +100,7 @@ class MainWindow(QMainWindow): tool_bar.addWidget(self._volume_slider) about_menu = self.menuBar().addMenu("&About") - about_qt_act = QAction("About &Qt", self, triggered=qApp.aboutQt) + about_qt_act = QAction("About &Qt", self, triggered=qApp.aboutQt) # noqa: F821 about_menu.addAction(about_qt_act) self._video_widget = QVideoWidget() @@ -168,8 +168,7 @@ class MainWindow(QMainWindow): @Slot("QMediaPlayer::PlaybackState") def update_buttons(self, state): media_count = len(self._playlist) - self._play_action.setEnabled(media_count > 0 - and state != QMediaPlayer.PlayingState) + self._play_action.setEnabled(media_count > 0 and state != QMediaPlayer.PlayingState) self._pause_action.setEnabled(state == QMediaPlayer.PlayingState) self._stop_action.setEnabled(state != QMediaPlayer.StoppedState) self._previous_action.setEnabled(self._player.position() > 0) diff --git a/examples/multimedia/screencapture/screenlistmodel.py b/examples/multimedia/screencapture/screenlistmodel.py index d3553aa2..72bb306e 100644 --- a/examples/multimedia/screencapture/screenlistmodel.py +++ b/examples/multimedia/screencapture/screenlistmodel.py @@ -9,7 +9,7 @@ class ScreenListModel(QAbstractListModel): def __init__(self, parent=None): super().__init__(parent) - app = qApp + app = qApp # noqa: F821 app.screenAdded.connect(self.screens_changed) app.screenRemoved.connect(self.screens_changed) app.primaryScreenChanged.connect(self.screens_changed) diff --git a/examples/network/blockingfortuneclient/blockingfortuneclient.py b/examples/network/blockingfortuneclient/blockingfortuneclient.py index f467cf8d..d0dd7e0a 100644 --- a/examples/network/blockingfortuneclient/blockingfortuneclient.py +++ b/examples/network/blockingfortuneclient/blockingfortuneclient.py @@ -5,12 +5,13 @@ """PySide6 port of the network/blockingfortunclient example from Qt v5.x, originating from PyQt""" from PySide6.QtCore import (Signal, QDataStream, QMutex, QMutexLocker, - QThread, QWaitCondition) + QThread, QWaitCondition) from PySide6.QtGui import QIntValidator from PySide6.QtWidgets import (QApplication, QDialogButtonBox, QGridLayout, - QLabel, QLineEdit, QMessageBox, QPushButton, QWidget) + QLabel, QLineEdit, QMessageBox, QPushButton, + QWidget) from PySide6.QtNetwork import (QAbstractSocket, QHostAddress, QNetworkInterface, - QTcpSocket) + QTcpSocket) class FortuneThread(QThread): @@ -109,7 +110,7 @@ class BlockingClient(QWidget): port_label.setBuddy(self._port_line_edit) self._status_label = QLabel( - "This example requires that you run the Fortune Server example as well.") + "This example requires that you run the Fortune Server example as well.") self._status_label.setWordWrap(True) self._get_fortune_button = QPushButton("Get Fortune") @@ -144,7 +145,7 @@ class BlockingClient(QWidget): def request_new_fortune(self): self._get_fortune_button.setEnabled(False) self.thread.request_new_fortune(self._host_line_edit.text(), - int(self._port_line_edit.text())) + int(self._port_line_edit.text())) def show_fortune(self, nextFortune): if nextFortune == self._current_fortune: @@ -158,22 +159,22 @@ class BlockingClient(QWidget): def display_error(self, socketError, message): if socketError == QAbstractSocket.HostNotFoundError: QMessageBox.information(self, "Blocking Fortune Client", - "The host was not found. Please check the host and port " - "settings.") + "The host was not found. Please check the host and port " + "settings.") elif socketError == QAbstractSocket.ConnectionRefusedError: QMessageBox.information(self, "Blocking Fortune Client", - "The connection was refused by the peer. Make sure the " - "fortune server is running, and check that the host name " - "and port settings are correct.") + "The connection was refused by the peer. Make sure the " + "fortune server is running, and check that the host name " + "and port settings are correct.") else: QMessageBox.information(self, "Blocking Fortune Client", - f"The following error occurred: {message}.") + f"The following error occurred: {message}.") self._get_fortune_button.setEnabled(True) def enable_get_fortune_button(self): - self._get_fortune_button.setEnabled(self._host_line_edit.text() != '' and - self._port_line_edit.text() != '') + self._get_fortune_button.setEnabled(self._host_line_edit.text() != '' + and self._port_line_edit.text() != '') if __name__ == '__main__': diff --git a/examples/network/downloader/downloader.py b/examples/network/downloader/downloader.py index f989adf3..fba0cb98 100644 --- a/examples/network/downloader/downloader.py +++ b/examples/network/downloader/downloader.py @@ -35,7 +35,8 @@ class DownloaderWidget(QWidget): self.link_box.setPlaceholderText("Download Link ...") self._open_folder_action = self.dest_box.addAction( - qApp.style().standardIcon(QStyle.SP_DirOpenIcon), QLineEdit.TrailingPosition + qApp.style().standardIcon(QStyle.SP_DirOpenIcon), # noqa: F821 + QLineEdit.TrailingPosition ) self._open_folder_action.triggered.connect(self.on_open_folder) diff --git a/examples/network/fortuneclient/fortuneclient.py b/examples/network/fortuneclient/fortuneclient.py index b695c2ea..e88e5e35 100644 --- a/examples/network/fortuneclient/fortuneclient.py +++ b/examples/network/fortuneclient/fortuneclient.py @@ -31,7 +31,7 @@ class Client(QDialog): port_label.setBuddy(self._port_line_edit) self._status_label = QLabel("This examples requires that you run " - "the Fortune Server example as well.") + "the Fortune Server example as well.") self._get_fortune_button = QPushButton("Get Fortune") self._get_fortune_button.setDefault(True) @@ -40,8 +40,7 @@ class Client(QDialog): quit_button = QPushButton("Quit") button_box = QDialogButtonBox() - button_box.addButton(self._get_fortune_button, - QDialogButtonBox.ActionRole) + button_box.addButton(self._get_fortune_button, QDialogButtonBox.ActionRole) button_box.addButton(quit_button, QDialogButtonBox.RejectRole) self._tcp_socket = QTcpSocket(self) @@ -69,7 +68,7 @@ class Client(QDialog): self._block_size = 0 self._tcp_socket.abort() self._tcp_socket.connectToHost(self._host_line_edit.text(), - int(self._port_line_edit.text())) + int(self._port_line_edit.text())) def read_fortune(self): instr = QDataStream(self._tcp_socket) @@ -99,23 +98,23 @@ class Client(QDialog): pass elif socketError == QAbstractSocket.HostNotFoundError: QMessageBox.information(self, "Fortune Client", - "The host was not found. Please check the host name and " - "port settings.") + "The host was not found. Please check the host name and " + "port settings.") elif socketError == QAbstractSocket.ConnectionRefusedError: QMessageBox.information(self, "Fortune Client", - "The connection was refused by the peer. Make sure the " - "fortune server is running, and check that the host name " - "and port settings are correct.") + "The connection was refused by the peer. Make sure the " + "fortune server is running, and check that the host name " + "and port settings are correct.") else: reason = self._tcp_socket.errorString() QMessageBox.information(self, "Fortune Client", - f"The following error occurred: {reason}.") + f"The following error occurred: {reason}.") self._get_fortune_button.setEnabled(True) def enable_get_fortune_button(self): - self._get_fortune_button.setEnabled(bool(self._host_line_edit.text() and - self._port_line_edit.text())) + self._get_fortune_button.setEnabled(bool(self._host_line_edit.text() + and self._port_line_edit.text())) if __name__ == '__main__': diff --git a/examples/network/fortuneserver/fortuneserver.py b/examples/network/fortuneserver/fortuneserver.py index d84c9dcf..a94a49f4 100644 --- a/examples/network/fortuneserver/fortuneserver.py +++ b/examples/network/fortuneserver/fortuneserver.py @@ -27,21 +27,21 @@ class Server(QDialog): if not self._tcp_server.listen(): reason = self._tcp_server.errorString() QMessageBox.critical(self, "Fortune Server", - f"Unable to start the server: {reason}.") + f"Unable to start the server: {reason}.") self.close() return port = self._tcp_server.serverPort() status_label.setText(f"The server is running on port {port}.\nRun the " - "Fortune Client example now.") + "Fortune Client example now.") self.fortunes = ( - "You've been leading a dog's life. Stay off the furniture.", - "You've got to think about tomorrow.", - "You will be surprised by a loud noise.", - "You will feel hungry again in another hour.", - "You might have mail.", - "You cannot kill time without injuring eternity.", - "Computers are not intelligent. They only think they are.") + "You've been leading a dog's life. Stay off the furniture.", + "You've got to think about tomorrow.", + "You will be surprised by a loud noise.", + "You will feel hungry again in another hour.", + "You might have mail.", + "You cannot kill time without injuring eternity.", + "Computers are not intelligent. They only think they are.") quit_button.clicked.connect(self.close) self._tcp_server.newConnection.connect(self.send_fortune) diff --git a/examples/network/googlesuggest/googlesuggest.py b/examples/network/googlesuggest/googlesuggest.py index 3cb9f757..d3fc67c0 100644 --- a/examples/network/googlesuggest/googlesuggest.py +++ b/examples/network/googlesuggest/googlesuggest.py @@ -120,12 +120,11 @@ class GSuggestCompletion(QObject): @Slot(QNetworkReply) def handle_network_data(self, network_reply: QNetworkReply): - url = network_reply.url() if network_reply.error() == QNetworkReply.NoError: choices: List[str] = [] response: QByteArray = network_reply.readAll() - xml = QXmlStreamReader(response) + xml = QXmlStreamReader(str(response)) while not xml.atEnd(): xml.readNext() if xml.tokenType() == QXmlStreamReader.StartElement: diff --git a/examples/network/threadedfortuneserver/threadedfortuneserver.py b/examples/network/threadedfortuneserver/threadedfortuneserver.py index 60900708..c75e2bc5 100644 --- a/examples/network/threadedfortuneserver/threadedfortuneserver.py +++ b/examples/network/threadedfortuneserver/threadedfortuneserver.py @@ -7,11 +7,11 @@ import random from PySide6.QtCore import (Signal, QByteArray, QDataStream, QIODevice, - QThread, Qt) + QThread, Qt) from PySide6.QtWidgets import (QApplication, QDialog, QHBoxLayout, QLabel, - QMessageBox, QPushButton, QVBoxLayout) + QMessageBox, QPushButton, QVBoxLayout) from PySide6.QtNetwork import (QHostAddress, QNetworkInterface, QTcpServer, - QTcpSocket) + QTcpSocket) class FortuneThread(QThread): @@ -75,7 +75,7 @@ class Dialog(QDialog): if not self.server.listen(): reason = self.server.errorString() QMessageBox.critical(self, "Threaded Fortune Server", - f"Unable to start the server: {reason}.") + f"Unable to start the server: {reason}.") self.close() return @@ -89,7 +89,7 @@ class Dialog(QDialog): port = self.server.serverPort() status_label.setText(f"The server is running on\n\nIP: {ip_address}\nport: {port}\n\n" - "Run the Fortune Client example now.") + "Run the Fortune Client example now.") quit_button.clicked.connect(self.close) diff --git a/examples/networkauth/redditclient/redditmodel.py b/examples/networkauth/redditclient/redditmodel.py index de81ceb1..fd6f9bc9 100644 --- a/examples/networkauth/redditclient/redditmodel.py +++ b/examples/networkauth/redditclient/redditmodel.py @@ -51,7 +51,7 @@ class RedditModel(QAbstractTableModel): document = QJsonDocument.fromJson(json) root_object = document.object() kind = root_object["kind"] - assert(kind == "Listing") + assert kind == "Listing" data_object = root_object["data"] children_array = data_object["children"] if not children_array: diff --git a/examples/networkauth/redditclient/redditwrapper.py b/examples/networkauth/redditclient/redditwrapper.py index f889a936..ed4c3d2c 100644 --- a/examples/networkauth/redditclient/redditwrapper.py +++ b/examples/networkauth/redditclient/redditwrapper.py @@ -19,6 +19,7 @@ NEW_URL = "https://oauth.reddit.com/new" HOT_URL = "https://oauth.reddit.com/hot" LIVE_THREADS_URL = "https://oauth.reddit.com/live/XXXX/about.json" + class RedditWrapper(QObject): authenticated = Signal() @@ -78,7 +79,7 @@ class RedditWrapper(QObject): json = reply.readAll() document = QJsonDocument.fromJson(json) - assert(document.isObject()) + assert document.isObject() root_object = document.object() data_object = root_object["data"] websocketUrl = QUrl(data_object["websocket_url"]) diff --git a/examples/opengl/contextinfo/contextinfo.py b/examples/opengl/contextinfo/contextinfo.py index 37c5f5d4..311d5b76 100644 --- a/examples/opengl/contextinfo/contextinfo.py +++ b/examples/opengl/contextinfo/contextinfo.py @@ -15,15 +15,14 @@ from PySide6.QtGui import (QMatrix4x4, QOpenGLContext, QSurfaceFormat, QWindow) from PySide6.QtOpenGL import (QOpenGLBuffer, QOpenGLShader, QOpenGLShaderProgram, QOpenGLVertexArrayObject) from PySide6.QtWidgets import (QApplication, QHBoxLayout, QMessageBox, QPlainTextEdit, - QWidget) + QWidget) from PySide6.support import VoidPtr try: from OpenGL import GL except ImportError: app = QApplication(sys.argv) message_box = QMessageBox(QMessageBox.Critical, "ContextInfo", - "PyOpenGL must be installed to run this example.", - QMessageBox.Close) + "PyOpenGL must be installed to run this example.", QMessageBox.Close) message_box.setDetailedText("Run:\npip install PyOpenGL PyOpenGL_accelerate") message_box.exec() sys.exit(1) @@ -74,7 +73,10 @@ colors = numpy.array([1, 0, 0, 0, 1, 0, 0, 0, 1], dtype=numpy.float32) def print_surface_format(surface_format): - profile_name = 'core' if surface_format.profile() == QSurfaceFormat.CoreProfile else 'compatibility' + if surface_format.profile() == QSurfaceFormat.CoreProfile: + profile_name = 'core' + else: + profile_name = 'compatibility' major = surface_format.majorVersion() minor = surface_format.minorVersion() return f"{profile_name} version {major}.{minor}" @@ -104,11 +106,13 @@ class RenderWindow(QWindow): # concept 3.2+ has. This may still fail since version 150 (3.2) is # specified in the sources but it's worth a try. if (fmt.renderableType() == QSurfaceFormat.OpenGL and fmt.majorVersion() == 3 - and fmt.minorVersion() <= 1): + and fmt.minorVersion() <= 1): use_new_style_shader = not fmt.testOption(QSurfaceFormat.DeprecatedFunctions) vertex_shader = vertex_shader_source if use_new_style_shader else vertex_shader_source_110 - fragment_shader = fragment_shader_source if use_new_style_shader else fragment_shader_source_110 + fragment_shader = (fragment_shader_source + if use_new_style_shader + else fragment_shader_source_110) if not self.program.addShaderFromSourceCode(QOpenGLShader.Vertex, vertex_shader): log = self.program.log() raise Exception("Vertex shader could not be added: {log} ({vertexShader})") diff --git a/examples/opengl/hellogl2/mainwindow.py b/examples/opengl/hellogl2/mainwindow.py index d19053a2..69b9b66f 100644 --- a/examples/opengl/hellogl2/mainwindow.py +++ b/examples/opengl/hellogl2/mainwindow.py @@ -16,7 +16,7 @@ class MainWindow(QMainWindow): menuWindow.addAction("Add new", QKeySequence(Qt.CTRL | Qt.Key_N), self.onAddNew) menuWindow.addAction("Quit", QKeySequence(Qt.CTRL | Qt.Key_Q), - qApp.closeAllWindows) + qApp.closeAllWindows) # noqa: F821 self.onAddNew() diff --git a/examples/opengl/hellogl2/window.py b/examples/opengl/hellogl2/window.py index c7b9946e..ad61d2f9 100644 --- a/examples/opengl/hellogl2/window.py +++ b/examples/opengl/hellogl2/window.py @@ -3,8 +3,7 @@ # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause from PySide6.QtCore import Slot, Qt -from PySide6.QtGui import QGuiApplication -from PySide6.QtWidgets import (QApplication, QHBoxLayout, QMainWindow, +from PySide6.QtWidgets import (QHBoxLayout, QMainWindow, QMessageBox, QPushButton, QSlider, QVBoxLayout, QWidget) @@ -12,7 +11,7 @@ from glwidget import GLWidget def _main_window(): - for t in qApp.topLevelWidgets(): + for t in qApp.topLevelWidgets(): # noqa: F821 if isinstance(t, QMainWindow): return t return None diff --git a/examples/opengl/textures/textures.py b/examples/opengl/textures/textures.py index 79f7761a..87c1164b 100644 --- a/examples/opengl/textures/textures.py +++ b/examples/opengl/textures/textures.py @@ -24,7 +24,7 @@ except ImportError: messageBox.exec() sys.exit(1) -import textures_rc +import textures_rc # noqa: F401 class GLWidget(QOpenGLWidget): @@ -32,12 +32,12 @@ class GLWidget(QOpenGLWidget): refCount = 0 coords = ( - ( ( +1, -1, -1 ), ( -1, -1, -1 ), ( -1, +1, -1 ), ( +1, +1, -1 ) ), - ( ( +1, +1, -1 ), ( -1, +1, -1 ), ( -1, +1, +1 ), ( +1, +1, +1 ) ), - ( ( +1, -1, +1 ), ( +1, -1, -1 ), ( +1, +1, -1 ), ( +1, +1, +1 ) ), - ( ( -1, -1, -1 ), ( -1, -1, +1 ), ( -1, +1, +1 ), ( -1, +1, -1 ) ), - ( ( +1, -1, +1 ), ( -1, -1, +1 ), ( -1, -1, -1 ), ( +1, -1, -1 ) ), - ( ( -1, -1, +1 ), ( +1, -1, +1 ), ( +1, +1, +1 ), ( -1, +1, +1 ) ) + ((+1, -1, -1), (-1, -1, -1), (-1, +1, -1), (+1, +1, -1)), + ((+1, +1, -1), (-1, +1, -1), (-1, +1, +1), (+1, +1, +1)), + ((+1, -1, +1), (+1, -1, -1), (+1, +1, -1), (+1, +1, +1)), + ((-1, -1, -1), (-1, -1, +1), (-1, +1, +1), (-1, +1, -1)), + ((+1, -1, +1), (-1, -1, +1), (-1, -1, -1), (+1, -1, -1)), + ((-1, -1, +1), (+1, -1, +1), (+1, +1, +1), (-1, +1, +1)) ) clicked = Signal() @@ -186,7 +186,7 @@ class Window(QWidget): mainLayout.addWidget(glw, i, j) glw.clicked.connect(self.setCurrentGlWidget) - qApp.lastWindowClosed.connect(glw.freeGLResources) + qApp.lastWindowClosed.connect(glw.freeGLResources) # noqa: F821 self.currentGlWidget = self.glWidgets[0][0] diff --git a/examples/opengl/threadedqopenglwidget/renderer.py b/examples/opengl/threadedqopenglwidget/renderer.py index fbed7942..81ec63cb 100644 --- a/examples/opengl/threadedqopenglwidget/renderer.py +++ b/examples/opengl/threadedqopenglwidget/renderer.py @@ -120,7 +120,7 @@ class Renderer(QObject, QOpenGLFunctions): if self._exiting: return - assert(ctx.thread() == QThread.currentThread()) + assert ctx.thread() == QThread.currentThread() # Make the context (and an offscreen surface) current for self thread. # The QOpenGLWidget's fbo is bound in the context. diff --git a/examples/pdf/quickpdfviewer/main.py b/examples/pdf/quickpdfviewer/main.py index 9d6ea68c..ee7b882c 100644 --- a/examples/pdf/quickpdfviewer/main.py +++ b/examples/pdf/quickpdfviewer/main.py @@ -11,7 +11,7 @@ from PySide6.QtQml import QQmlApplicationEngine from PySide6.QtGui import QGuiApplication from PySide6.QtCore import QCoreApplication, QUrl -import rc_viewer +import rc_viewer # noqa: F401 """PySide6 port of the pdf/pdfviewer example from Qt v6.x""" diff --git a/examples/qml/editingmodel/doc/editingmodel.rst b/examples/qml/editingmodel/doc/editingmodel.rst index 60bb24f5..2b45b23f 100644 --- a/examples/qml/editingmodel/doc/editingmodel.rst +++ b/examples/qml/editingmodel/doc/editingmodel.rst @@ -1,6 +1,8 @@ QAbstractListModel in QML ========================= +.. tags:: Android + This example shows how to add, remove and move items inside a QML ListView, but showing and editing the data via roles using a QAbstractListModel from Python. diff --git a/examples/qml/editingmodel/main.py b/examples/qml/editingmodel/main.py index 00b3ae2b..5240a9de 100644 --- a/examples/qml/editingmodel/main.py +++ b/examples/qml/editingmodel/main.py @@ -8,7 +8,7 @@ from PySide6.QtCore import QUrl from PySide6.QtGui import QGuiApplication from PySide6.QtQml import QQmlApplicationEngine -from model import BaseModel +from model import BaseModel # noqa: F401 if __name__ == "__main__": app = QGuiApplication(sys.argv) diff --git a/examples/qml/editingmodel/model.py b/examples/qml/editingmodel/model.py index 59149787..02a1e571 100644 --- a/examples/qml/editingmodel/model.py +++ b/examples/qml/editingmodel/model.py @@ -2,8 +2,7 @@ # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause -from PySide6.QtCore import (QAbstractListModel, QByteArray, QModelIndex, Qt, - Slot) +from PySide6.QtCore import QAbstractListModel, QByteArray, QModelIndex, Qt, Slot from PySide6.QtGui import QColor from PySide6.QtQml import QmlElement @@ -104,18 +103,18 @@ class BaseModel(QAbstractListModel): self.beginMoveRows(QModelIndex(), sourceRow, sourceRow + count, QModelIndex(), end) # start database work - pops = self.db[sourceRow : sourceRow + count + 1] + pops = self.db[sourceRow: sourceRow + count + 1] if sourceRow > dstChild: self.db = ( self.db[:dstChild] + pops + self.db[dstChild:sourceRow] - + self.db[sourceRow + count + 1 :] + + self.db[sourceRow + count + 1:] ) else: start = self.db[:sourceRow] - middle = self.db[dstChild : dstChild + 1] - endlist = self.db[dstChild + count + 1 :] + middle = self.db[dstChild: dstChild + 1] + endlist = self.db[dstChild + count + 1:] self.db = start + middle + pops + endlist # end database work @@ -136,7 +135,7 @@ class BaseModel(QAbstractListModel): self.beginRemoveRows(QModelIndex(), row, row + count) # start database work - self.db = self.db[:row] + self.db[row + count + 1 :] + self.db = self.db[:row] + self.db[row + count + 1:] # end database work self.endRemoveRows() diff --git a/examples/qml/tutorials/extending-qml-advanced/adding/main.py b/examples/qml/tutorials/extending-qml-advanced/adding/main.py index faf13f42..ec703dbf 100644 --- a/examples/qml/tutorials/extending-qml-advanced/adding/main.py +++ b/examples/qml/tutorials/extending-qml-advanced/adding/main.py @@ -9,7 +9,7 @@ import sys from PySide6.QtCore import QCoreApplication from PySide6.QtQml import QQmlComponent, QQmlEngine -from person import Person +from person import Person # noqa: F401 if __name__ == '__main__': diff --git a/examples/qml/tutorials/extending-qml-advanced/adding/person.py b/examples/qml/tutorials/extending-qml-advanced/adding/person.py index fafb9d58..526eae71 100644 --- a/examples/qml/tutorials/extending-qml-advanced/adding/person.py +++ b/examples/qml/tutorials/extending-qml-advanced/adding/person.py @@ -32,4 +32,3 @@ class Person(QObject): @shoe_size.setter def shoe_size(self, s): self._shoe_size = s - diff --git a/examples/qml/tutorials/extending-qml-advanced/advanced1-Base-project/main.py b/examples/qml/tutorials/extending-qml-advanced/advanced1-Base-project/main.py index dc07a2a5..560db660 100644 --- a/examples/qml/tutorials/extending-qml-advanced/advanced1-Base-project/main.py +++ b/examples/qml/tutorials/extending-qml-advanced/advanced1-Base-project/main.py @@ -1,7 +1,8 @@ # Copyright (C) 2023 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause -"""PySide6 port of the qml/examples/qml/tutorials/extending-qml-advanced/advanced1-Base-project example from Qt v6.x""" +"""PySide6 port of the + qml/examples/qml/tutorials/extending-qml-advanced/advanced1-Base-project example from Qt v6.x""" from pathlib import Path import sys @@ -9,8 +10,8 @@ import sys from PySide6.QtCore import QCoreApplication from PySide6.QtQml import QQmlComponent, QQmlEngine -from person import Person -from birthdayparty import BirthdayParty +from person import Person # noqa: F401 +from birthdayparty import BirthdayParty # noqa: F401 app = QCoreApplication(sys.argv) diff --git a/examples/qml/tutorials/extending-qml-advanced/advanced2-Inheritance-and-coercion/main.py b/examples/qml/tutorials/extending-qml-advanced/advanced2-Inheritance-and-coercion/main.py index ef4b5ac5..cc77e2b4 100644 --- a/examples/qml/tutorials/extending-qml-advanced/advanced2-Inheritance-and-coercion/main.py +++ b/examples/qml/tutorials/extending-qml-advanced/advanced2-Inheritance-and-coercion/main.py @@ -1,17 +1,18 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause -"""PySide6 port of the qml/examples/qml/tutorials/extending-qml-advanced/advanced2-Inheritance-and-coercion example from Qt v6.x""" +"""PySide6 port of the + qml/examples/qml/tutorials/extending-qml-advanced/advanced2-Inheritance-and-coercion example + from Qt v6.x""" from pathlib import Path -import os import sys from PySide6.QtCore import QCoreApplication from PySide6.QtQml import QQmlComponent, QQmlEngine -from person import Boy, Girl -from birthdayparty import BirthdayParty +from person import Boy, Girl # noqa: F401 +from birthdayparty import BirthdayParty # noqa: F401 app = QCoreApplication(sys.argv) diff --git a/examples/qml/tutorials/extending-qml-advanced/advanced3-Default-properties/main.py b/examples/qml/tutorials/extending-qml-advanced/advanced3-Default-properties/main.py index f469538b..020974c9 100644 --- a/examples/qml/tutorials/extending-qml-advanced/advanced3-Default-properties/main.py +++ b/examples/qml/tutorials/extending-qml-advanced/advanced3-Default-properties/main.py @@ -1,7 +1,9 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause -"""PySide6 port of the qml/examples/qml/tutorials/extending-qml-advanced/default advanced3-Default-properties example from Qt v6.x""" +"""PySide6 port of the + qml/examples/qml/tutorials/extending-qml-advanced/default advanced3-Default-properties example + from Qt v6.x""" from pathlib import Path import sys @@ -9,8 +11,8 @@ import sys from PySide6.QtCore import QCoreApplication from PySide6.QtQml import QQmlComponent, QQmlEngine -from person import Boy, Girl -from birthdayparty import BirthdayParty +from person import Boy, Girl # noqa: F401 +from birthdayparty import BirthdayParty # noqa: F401 app = QCoreApplication(sys.argv) diff --git a/examples/qml/tutorials/extending-qml-advanced/advanced4-Grouped-properties/main.py b/examples/qml/tutorials/extending-qml-advanced/advanced4-Grouped-properties/main.py index 3cdddbd0..9757b6da 100644 --- a/examples/qml/tutorials/extending-qml-advanced/advanced4-Grouped-properties/main.py +++ b/examples/qml/tutorials/extending-qml-advanced/advanced4-Grouped-properties/main.py @@ -1,7 +1,9 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause -"""PySide6 port of the qml/examples/qml/tutorials/extending-qml-advanced/advanced4-Grouped-properties example from Qt v6.x""" +"""PySide6 port of the + qml/examples/qml/tutorials/extending-qml-advanced/advanced4-Grouped-properties example + from Qt v6.x""" from pathlib import Path import sys @@ -9,8 +11,8 @@ import sys from PySide6.QtCore import QCoreApplication from PySide6.QtQml import QQmlComponent, QQmlEngine -from person import Boy, Girl -from birthdayparty import BirthdayParty +from person import Boy, Girl # noqa: F401 +from birthdayparty import BirthdayParty # noqa: F401 if __name__ == '__main__': diff --git a/examples/qml/tutorials/extending-qml-advanced/advanced5-Attached-properties/main.py b/examples/qml/tutorials/extending-qml-advanced/advanced5-Attached-properties/main.py index bb607ac7..9a92afeb 100644 --- a/examples/qml/tutorials/extending-qml-advanced/advanced5-Attached-properties/main.py +++ b/examples/qml/tutorials/extending-qml-advanced/advanced5-Attached-properties/main.py @@ -1,7 +1,9 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause -"""PySide6 port of the qml/examples/qml/tutorials/extending-qml-advanced/advanced5-Attached-properties example from Qt v6.x""" +"""PySide6 port of the + qml/examples/qml/tutorials/extending-qml-advanced/advanced5-Attached-properties example + from Qt v6.x""" from pathlib import Path import sys @@ -9,8 +11,8 @@ import sys from PySide6.QtCore import QCoreApplication from PySide6.QtQml import QQmlComponent, QQmlEngine, qmlAttachedPropertiesObject -from person import Boy, Girl -from birthdayparty import BirthdayParty +from person import Boy, Girl # noqa: F401 +from birthdayparty import BirthdayParty # noqa: F401 app = QCoreApplication(sys.argv) diff --git a/examples/qml/tutorials/extending-qml-advanced/advanced6-Property-value-source/main.py b/examples/qml/tutorials/extending-qml-advanced/advanced6-Property-value-source/main.py index b763a456..ea412a54 100644 --- a/examples/qml/tutorials/extending-qml-advanced/advanced6-Property-value-source/main.py +++ b/examples/qml/tutorials/extending-qml-advanced/advanced6-Property-value-source/main.py @@ -1,7 +1,9 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause -"""PySide6 port of the qml/examples/qml/tutorials/extending-qml-advanced/advanced6-Property-value-source example from Qt v6.x""" +"""PySide6 port of the + qml/examples/qml/tutorials/extending-qml-advanced/advanced6-Property-value-source example + from Qt v6.x""" from pathlib import Path import sys @@ -9,9 +11,9 @@ import sys from PySide6.QtCore import QCoreApplication from PySide6.QtQml import QQmlComponent, QQmlEngine, qmlAttachedPropertiesObject -from person import Boy, Girl +from person import Boy, Girl # noqa: F401 from birthdayparty import BirthdayParty -from happybirthdaysong import HappyBirthdaySong +from happybirthdaysong import HappyBirthdaySong # noqa: F401 app = QCoreApplication(sys.argv) diff --git a/examples/qml/tutorials/extending-qml-advanced/binding/main.py b/examples/qml/tutorials/extending-qml-advanced/binding/main.py index 738bab08..64929a80 100644 --- a/examples/qml/tutorials/extending-qml-advanced/binding/main.py +++ b/examples/qml/tutorials/extending-qml-advanced/binding/main.py @@ -9,9 +9,9 @@ import sys from PySide6.QtCore import QCoreApplication from PySide6.QtQml import QQmlComponent, QQmlEngine, qmlAttachedPropertiesObject -from person import Boy, Girl -from birthdayparty import BirthdayParty -from happybirthdaysong import HappyBirthdaySong +from person import Boy, Girl # noqa: F401 +from birthdayparty import BirthdayParty # noqa: F401 +from happybirthdaysong import HappyBirthdaySong # noqa: F401 if __name__ == "__main__": diff --git a/examples/qml/tutorials/extending-qml-advanced/methods/main.py b/examples/qml/tutorials/extending-qml-advanced/methods/main.py index 5f51ebe1..fb656f26 100644 --- a/examples/qml/tutorials/extending-qml-advanced/methods/main.py +++ b/examples/qml/tutorials/extending-qml-advanced/methods/main.py @@ -9,8 +9,8 @@ import sys from PySide6.QtCore import QCoreApplication from PySide6.QtQml import QQmlComponent, QQmlEngine -from person import Person -from birthdayparty import BirthdayParty +from person import Person # noqa: F401 +from birthdayparty import BirthdayParty # noqa: F401 app = QCoreApplication(sys.argv) diff --git a/examples/qml/tutorials/extending-qml-advanced/properties/main.py b/examples/qml/tutorials/extending-qml-advanced/properties/main.py index 4797e4c7..11757d5f 100644 --- a/examples/qml/tutorials/extending-qml-advanced/properties/main.py +++ b/examples/qml/tutorials/extending-qml-advanced/properties/main.py @@ -9,8 +9,8 @@ import sys from PySide6.QtCore import QCoreApplication from PySide6.QtQml import QQmlComponent, QQmlEngine -from person import Person -from birthdayparty import BirthdayParty +from person import Person # noqa: F401 +from birthdayparty import BirthdayParty # noqa: F401 if __name__ == '__main__': diff --git a/examples/qml/tutorials/extending-qml-advanced/properties/person.py b/examples/qml/tutorials/extending-qml-advanced/properties/person.py index fafb9d58..526eae71 100644 --- a/examples/qml/tutorials/extending-qml-advanced/properties/person.py +++ b/examples/qml/tutorials/extending-qml-advanced/properties/person.py @@ -32,4 +32,3 @@ class Person(QObject): @shoe_size.setter def shoe_size(self, s): self._shoe_size = s - diff --git a/examples/qml/tutorials/extending-qml/chapter4-customPropertyTypes/customPropertyTypes.py b/examples/qml/tutorials/extending-qml/chapter4-customPropertyTypes/customPropertyTypes.py index c70d7fc4..659850f3 100644 --- a/examples/qml/tutorials/extending-qml/chapter4-customPropertyTypes/customPropertyTypes.py +++ b/examples/qml/tutorials/extending-qml/chapter4-customPropertyTypes/customPropertyTypes.py @@ -1,7 +1,8 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause -"""PySide6 port of the qml/tutorials/extending-qml/chapter4-customPropertyTypes example from Qt v5.x""" +"""PySide6 port of the qml/tutorials/extending-qml/chapter4-customPropertyTypes example + from Qt v5.x""" import os from pathlib import Path diff --git a/examples/qml/tutorials/extending-qml/chapter5-listproperties/listproperties.py b/examples/qml/tutorials/extending-qml/chapter5-listproperties/listproperties.py index 236c487f..98952cef 100644 --- a/examples/qml/tutorials/extending-qml/chapter5-listproperties/listproperties.py +++ b/examples/qml/tutorials/extending-qml/chapter5-listproperties/listproperties.py @@ -54,7 +54,8 @@ class PieSlice (QQuickPaintedItem): pen = QPen(self._color, 2) painter.setPen(pen) painter.setRenderHints(QPainter.Antialiasing, True) - painter.drawPie(self.boundingRect().adjusted(1, 1, -1, -1), self._fromAngle * 16, self._angleSpan * 16) + painter.drawPie( + self.boundingRect().adjusted(1, 1, -1, -1), self._fromAngle * 16, self._angleSpan * 16) @QmlElement diff --git a/examples/qml/tutorials/extending-qml/chapter6-plugins/Charts/piechart.py b/examples/qml/tutorials/extending-qml/chapter6-plugins/Charts/piechart.py index 6a7e80cc..3ab8bcc0 100644 --- a/examples/qml/tutorials/extending-qml/chapter6-plugins/Charts/piechart.py +++ b/examples/qml/tutorials/extending-qml/chapter6-plugins/Charts/piechart.py @@ -12,6 +12,7 @@ from pieslice import PieSlice QML_IMPORT_NAME = "Charts" QML_IMPORT_MAJOR_VERSION = 1 + @QmlElement class PieChart(QQuickItem): def __init__(self, parent=None): diff --git a/examples/qml/tutorials/extending-qml/chapter6-plugins/Charts/pieslice.py b/examples/qml/tutorials/extending-qml/chapter6-plugins/Charts/pieslice.py index 67242a96..6f82f1f1 100644 --- a/examples/qml/tutorials/extending-qml/chapter6-plugins/Charts/pieslice.py +++ b/examples/qml/tutorials/extending-qml/chapter6-plugins/Charts/pieslice.py @@ -11,6 +11,7 @@ from PySide6.QtQml import QmlElement QML_IMPORT_NAME = "Charts" QML_IMPORT_MAJOR_VERSION = 1 + @QmlElement class PieSlice(QQuickPaintedItem): def __init__(self, parent=None): diff --git a/examples/qml/usingmodel/doc/usingmodel.rst b/examples/qml/usingmodel/doc/usingmodel.rst index 11b476d0..06a1b27b 100644 --- a/examples/qml/usingmodel/doc/usingmodel.rst +++ b/examples/qml/usingmodel/doc/usingmodel.rst @@ -1,6 +1,8 @@ Using Model Example =================== +.. tags:: Android + A Python application that demonstrates how to use a :ref:`QAbstractListModel` with QML. diff --git a/examples/qml/usingmodel/usingmodel.py b/examples/qml/usingmodel/usingmodel.py index 6f8ea5a2..008a1b94 100644 --- a/examples/qml/usingmodel/usingmodel.py +++ b/examples/qml/usingmodel/usingmodel.py @@ -2,24 +2,37 @@ # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause import os +from dataclasses import dataclass from pathlib import Path import sys from PySide6.QtCore import QAbstractListModel, Qt, QUrl, QByteArray from PySide6.QtGui import QGuiApplication from PySide6.QtQuick import QQuickView -from PySide6.QtQml import qmlRegisterSingletonType +from PySide6.QtQml import QmlElement, QmlSingleton +QML_IMPORT_NAME = "PersonModel" +QML_IMPORT_MAJOR_VERSION = 1 + + +@dataclass +class Person: + name: str + myrole: str + + +@QmlElement +@QmlSingleton class PersonModel (QAbstractListModel): MyRole = Qt.UserRole + 1 - def __init__(self, parent=None): - QAbstractListModel.__init__(self, parent) - self._data = [] + def __init__(self, data, parent=None): + super().__init__(parent) + self._data = data def roleNames(self): roles = { - PersonModel.MyRole: QByteArray(b'modelData'), + PersonModel.MyRole: QByteArray(b'myrole'), Qt.DisplayRole: QByteArray(b'display') } return roles @@ -29,26 +42,18 @@ class PersonModel (QAbstractListModel): def data(self, index, role): d = self._data[index.row()] - if role == Qt.DisplayRole: - return d['name'] - elif role == Qt.DecorationRole: + return d.name + if role == Qt.DecorationRole: return Qt.black - elif role == PersonModel.MyRole: - return d['myrole'] + if role == PersonModel.MyRole: + return d.myrole return None - def populate(self, data=None): - for item in data: - self._data.append(item) - - -def model_callback(engine): - my_model = PersonModel() - data = [{'name': 'Qt', 'myrole': 'role1'}, - {'name': 'PySide', 'myrole': 'role2'}] - my_model.populate(data) - return my_model + @staticmethod + def create(engine): + data = [Person('Qt', 'myrole'), Person('PySide', 'role2')] + return PersonModel(data) if __name__ == '__main__': @@ -56,7 +61,6 @@ if __name__ == '__main__': view = QQuickView() view.setResizeMode(QQuickView.SizeRootObjectToView) - qmlRegisterSingletonType(PersonModel, "PersonModel", 1, 0, "MyModel", model_callback) qml_file = os.fspath(Path(__file__).resolve().parent / 'view.qml') view.setSource(QUrl.fromLocalFile(qml_file)) if view.status() == QQuickView.Error: diff --git a/examples/qml/usingmodel/view.qml b/examples/qml/usingmodel/view.qml index c5aa7e0f..e8b1fb2f 100644 --- a/examples/qml/usingmodel/view.qml +++ b/examples/qml/usingmodel/view.qml @@ -8,21 +8,13 @@ ListView { width: 100 height: 100 anchors.fill: parent - model: MyModel + model: PersonModel delegate: Component { Rectangle { height: 25 width: 100 Text { - function displayText() { - var result = "" - if (typeof display !== "undefined") - result = display + ": " - result += modelData - return result - } - - text: displayText() + text: display + ": " + myrole } } } diff --git a/examples/quick/models/objectlistmodel/doc/objectlistmodel.rst b/examples/quick/models/objectlistmodel/doc/objectlistmodel.rst index d71ee61d..a4af6270 100644 --- a/examples/quick/models/objectlistmodel/doc/objectlistmodel.rst +++ b/examples/quick/models/objectlistmodel/doc/objectlistmodel.rst @@ -1,6 +1,8 @@ Object List Model Example ========================= +.. tags:: Android + A list of QObject values can also be used as a model. A list[QObject,] provides the properties of the objects in the list as roles. diff --git a/examples/quick/models/objectlistmodel/objectlistmodel.py b/examples/quick/models/objectlistmodel/objectlistmodel.py index 0843ae48..968761e5 100644 --- a/examples/quick/models/objectlistmodel/objectlistmodel.py +++ b/examples/quick/models/objectlistmodel/objectlistmodel.py @@ -9,6 +9,7 @@ from PySide6.QtQuick import QQuickView # This example illustrates exposing a list of QObjects as a model in QML + class DataObject(QObject): nameChanged = Signal() @@ -25,7 +26,7 @@ class DataObject(QObject): def setName(self, name): if name != self._name: self._name = name - nameChanged.emit() + self.nameChanged.emit() def color(self): return self._color @@ -33,8 +34,7 @@ class DataObject(QObject): def setColor(self, color): if color != self._color: self._color = color - colorChanged.emit() - + self.colorChanged.emit() name = Property(str, name, setName, notify=nameChanged) color = Property(str, color, setColor, notify=colorChanged) diff --git a/examples/quick/models/stringlistmodel/doc/stringlistmodel.rst b/examples/quick/models/stringlistmodel/doc/stringlistmodel.rst index 4c00ed13..ce11674b 100644 --- a/examples/quick/models/stringlistmodel/doc/stringlistmodel.rst +++ b/examples/quick/models/stringlistmodel/doc/stringlistmodel.rst @@ -1,6 +1,8 @@ String List Model Example ========================= +.. tags:: Android + A model may be a simple 'list', which provides the contents of the list via the modelData role. diff --git a/examples/quick/models/stringlistmodel/stringlistmodel.py b/examples/quick/models/stringlistmodel/stringlistmodel.py index a7a1807b..3982b1ff 100644 --- a/examples/quick/models/stringlistmodel/stringlistmodel.py +++ b/examples/quick/models/stringlistmodel/stringlistmodel.py @@ -15,7 +15,7 @@ if __name__ == '__main__': dataList = ["Item 1", "Item 2", "Item 3", "Item 4"] view = QQuickView() - view.setInitialProperties({"model": dataList }) + view.setInitialProperties({"model": dataList}) qml_file = Path(__file__).parent / "view.qml" view.setSource(QUrl.fromLocalFile(qml_file)) diff --git a/examples/quick/painteditem/doc/painteditem.rst b/examples/quick/painteditem/doc/painteditem.rst index 6f12355c..5c3e8935 100644 --- a/examples/quick/painteditem/doc/painteditem.rst +++ b/examples/quick/painteditem/doc/painteditem.rst @@ -1,6 +1,8 @@ Scene Graph Painted Item Example ================================ +.. tags:: Android + Shows how to implement QPainter-based custom scenegraph items. The Painted Item example shows how to use the QML Scene Graph framework to diff --git a/examples/quick/rendercontrol/rendercontrol_opengl/cuberenderer.py b/examples/quick/rendercontrol/rendercontrol_opengl/cuberenderer.py index ae56988a..69e7321f 100644 --- a/examples/quick/rendercontrol/rendercontrol_opengl/cuberenderer.py +++ b/examples/quick/rendercontrol/rendercontrol_opengl/cuberenderer.py @@ -45,27 +45,27 @@ VERTEXES = numpy.array([-0.5, 0.5, 0.5, 0.5, -0.5, 0.5, -0.5, -0.5, 0.5, -0.5, 0.5, -0.5, -0.5, -0.5, 0.5, -0.5, -0.5, -0.5, -0.5, -0.5, 0.5, -0.5, 0.5, -0.5, -0.5, 0.5, 0.5, - 0.5, 0.5, -0.5, -0.5, 0.5, 0.5, -0.5, 0.5, -0.5, - -0.5, 0.5, 0.5, 0.5, 0.5, -0.5, 0.5, 0.5, 0.5, - -0.5, -0.5, -0.5, -0.5, -0.5, 0.5, 0.5, -0.5, -0.5, - 0.5, -0.5, 0.5, 0.5, -0.5, -0.5, -0.5, -0.5, 0.5], + 0.5, 0.5, -0.5, -0.5, 0.5, 0.5, -0.5, 0.5, -0.5, + -0.5, 0.5, 0.5, 0.5, 0.5, -0.5, 0.5, 0.5, 0.5, + -0.5, -0.5, -0.5, -0.5, -0.5, 0.5, 0.5, -0.5, -0.5, + 0.5, -0.5, 0.5, 0.5, -0.5, -0.5, -0.5, -0.5, 0.5], dtype=numpy.float32) -TEX_COORDS = numpy.array([0.0, 0.0, 1.0, 1.0, 1.0, 0.0, - 1.0, 1.0, 0.0, 0.0, 0.0, 1.0, - 1.0, 1.0, 1.0, 0.0, 0.0, 1.0, - 0.0, 0.0, 0.0, 1.0, 1.0, 0.0, +TEX_COORDS = numpy.array([0.0, 0.0, 1.0, 1.0, 1.0, 0.0, + 1.0, 1.0, 0.0, 0.0, 0.0, 1.0, + 1.0, 1.0, 1.0, 0.0, 0.0, 1.0, + 0.0, 0.0, 0.0, 1.0, 1.0, 0.0, - 1.0, 1.0, 1.0, 0.0, 0.0, 1.0, - 0.0, 0.0, 0.0, 1.0, 1.0, 0.0, - 0.0, 0.0, 1.0, 1.0, 1.0, 0.0, - 1.0, 1.0, 0.0, 0.0, 0.0, 1.0, + 1.0, 1.0, 1.0, 0.0, 0.0, 1.0, + 0.0, 0.0, 0.0, 1.0, 1.0, 0.0, + 0.0, 0.0, 1.0, 1.0, 1.0, 0.0, + 1.0, 1.0, 0.0, 0.0, 0.0, 1.0, - 0.0, 1.0, 1.0, 0.0, 1.0, 1.0, - 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, - 1.0, 0.0, 1.0, 1.0, 0.0, 0.0, - 0.0, 1.0, 0.0, 0.0, 1.0, 1.0], dtype=numpy.float32) + 0.0, 1.0, 1.0, 0.0, 1.0, 1.0, + 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, + 1.0, 0.0, 1.0, 1.0, 0.0, 0.0, + 0.0, 1.0, 0.0, 0.0, 1.0, 1.0], dtype=numpy.float32) class CubeRenderer(): @@ -113,7 +113,6 @@ class CubeRenderer(): self.m_vao = QOpenGLVertexArrayObject() self.m_vao.create() - vaoBinder = QOpenGLVertexArrayObject.Binder(self.m_vao) self.m_vbo = QOpenGLBuffer() self.m_vbo.create() @@ -166,7 +165,6 @@ class CubeRenderer(): f.glEnable(GL_DEPTH_TEST) self.m_program.bind() - vaoBinder = QOpenGLVertexArrayObject.Binder(self.m_vao) # If VAOs are not supported, set the vertex attributes every time. if not self.m_vao.isCreated(): self.setupVertexAttribs() diff --git a/examples/quick/rendercontrol/rendercontrol_opengl/window_singlethreaded.py b/examples/quick/rendercontrol/rendercontrol_opengl/window_singlethreaded.py index 05164769..6f1e61f9 100644 --- a/examples/quick/rendercontrol/rendercontrol_opengl/window_singlethreaded.py +++ b/examples/quick/rendercontrol/rendercontrol_opengl/window_singlethreaded.py @@ -247,7 +247,7 @@ class WindowSingleThreaded(QWindow): # If self is a resize after the scene is up and running, recreate the # texture and the Quick item and scene. if (self.texture_id() - and self.m_textureSize != self.size() * self.devicePixelRatio()): + and self.m_textureSize != self.size() * self.devicePixelRatio()): self.resizeTexture() @Slot() diff --git a/examples/quick/scenegraph/openglunderqml/main.py b/examples/quick/scenegraph/openglunderqml/main.py index a79d1bed..0e24877b 100644 --- a/examples/quick/scenegraph/openglunderqml/main.py +++ b/examples/quick/scenegraph/openglunderqml/main.py @@ -8,7 +8,7 @@ from PySide6.QtCore import QUrl from PySide6.QtGui import QGuiApplication from PySide6.QtQuick import QQuickView, QQuickWindow, QSGRendererInterface -from squircle import Squircle +from squircle import Squircle # noqa: F401 if __name__ == "__main__": app = QGuiApplication(sys.argv) diff --git a/examples/quick/window/main.py b/examples/quick/window/main.py index 549e4251..62ba6a5e 100644 --- a/examples/quick/window/main.py +++ b/examples/quick/window/main.py @@ -11,12 +11,12 @@ from PySide6.QtQml import QQmlComponent, QQmlEngine from PySide6.QtQuick import QQuickWindow from PySide6.QtQuickControls2 import QQuickStyle -import rc_window +import rc_window # noqa: F401 # Append the parent directory of this file so that Python can find and # import from the "shared" sibling directory. sys.path.append(os.fspath(Path(__file__).parent.parent)) -from shared import shared_rc +from shared import shared_rc # noqa: F401, E402 if __name__ == "__main__": diff --git a/examples/quick3d/customgeometry/main.py b/examples/quick3d/customgeometry/main.py index ff1ce384..148330d9 100644 --- a/examples/quick3d/customgeometry/main.py +++ b/examples/quick3d/customgeometry/main.py @@ -11,9 +11,9 @@ from PySide6.QtQml import QQmlApplicationEngine from PySide6.QtQuick3D import QQuick3D # Imports to trigger the resources and registration of QML elements -import resources_rc -from examplepoint import ExamplePointGeometry -from exampletriangle import ExampleTriangleGeometry +import resources_rc # noqa: F401 +from examplepoint import ExamplePointGeometry # noqa: F401 +from exampletriangle import ExampleTriangleGeometry # noqa: F401 if __name__ == "__main__": os.environ["QT_QUICK_CONTROLS_STYLE"] = "Basic" diff --git a/examples/quick3d/proceduraltexture/ProceduralTextureModule/Main.qml b/examples/quick3d/proceduraltexture/ProceduralTextureModule/Main.qml index b6dd47de..610c08a2 100644 --- a/examples/quick3d/proceduraltexture/ProceduralTextureModule/Main.qml +++ b/examples/quick3d/proceduraltexture/ProceduralTextureModule/Main.qml @@ -82,7 +82,7 @@ ApplicationWindow { ); } - function generateTextureData() : ArrayBuffer { + function generateTextureData() { let dataBuffer = new ArrayBuffer(width * height * 4) let data = new Uint8Array(dataBuffer) diff --git a/examples/quick3d/proceduraltexture/gradienttexture.py b/examples/quick3d/proceduraltexture/gradienttexture.py index 1511905c..a577f7eb 100644 --- a/examples/quick3d/proceduraltexture/gradienttexture.py +++ b/examples/quick3d/proceduraltexture/gradienttexture.py @@ -103,4 +103,3 @@ class GradientTexture(QQuick3DTextureData): output.setBlueF(color1.blueF() + (value * (color2.blueF() - color1.blueF()))) return output - diff --git a/examples/quick3d/proceduraltexture/main.py b/examples/quick3d/proceduraltexture/main.py index 971b0ecb..a732cc01 100644 --- a/examples/quick3d/proceduraltexture/main.py +++ b/examples/quick3d/proceduraltexture/main.py @@ -4,8 +4,11 @@ from PySide6.QtGui import QGuiApplication from PySide6.QtQml import QQmlApplicationEngine -from gradienttexture import GradientTexture +from gradienttexture import GradientTexture # noqa: F401 +from pathlib import Path + +import os import sys if __name__ == "__main__": diff --git a/examples/quickcontrols/contactslist/Contact/ContactDelegate.ui.qml b/examples/quickcontrols/contactslist/Contact/ContactDelegate.ui.qml new file mode 100644 index 00000000..affcccc3 --- /dev/null +++ b/examples/quickcontrols/contactslist/Contact/ContactDelegate.ui.qml @@ -0,0 +1,82 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +import QtQuick +import QtQuick.Layouts +import QtQuick.Controls + +ItemDelegate { + id: delegate + + checkable: true + + contentItem: ColumnLayout { + spacing: 10 + + Label { + text: fullName + font.bold: true + elide: Text.ElideRight + Layout.fillWidth: true + } + + GridLayout { + id: grid + visible: false + + columns: 2 + rowSpacing: 10 + columnSpacing: 10 + + Label { + text: qsTr("Address:") + Layout.leftMargin: 60 + } + + Label { + text: address + font.bold: true + elide: Text.ElideRight + Layout.fillWidth: true + } + + Label { + text: qsTr("City:") + Layout.leftMargin: 60 + } + + Label { + text: city + font.bold: true + elide: Text.ElideRight + Layout.fillWidth: true + } + + Label { + text: qsTr("Number:") + Layout.leftMargin: 60 + } + + Label { + text: number + font.bold: true + elide: Text.ElideRight + Layout.fillWidth: true + } + } + } + + states: [ + State { + name: "expanded" + when: delegate.checked + + PropertyChanges { + // TODO: When Qt Design Studio supports generalized grouped properties, change to: + // grid.visible: true + target: grid + visible: true + } + } + ] +} diff --git a/examples/quickcontrols/contactslist/Contact/ContactDialog.qml b/examples/quickcontrols/contactslist/Contact/ContactDialog.qml new file mode 100644 index 00000000..d906f00e --- /dev/null +++ b/examples/quickcontrols/contactslist/Contact/ContactDialog.qml @@ -0,0 +1,45 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +import QtQuick +import QtQuick.Controls + +Dialog { + id: dialog + + signal finished(string fullName, string address, string city, string number) + + function createContact() { + form.fullName.clear(); + form.address.clear(); + form.city.clear(); + form.number.clear(); + + dialog.title = qsTr("Add Contact"); + dialog.open(); + } + + function editContact(contact) { + form.fullName.text = contact.fullName; + form.address.text = contact.address; + form.city.text = contact.city; + form.number.text = contact.number; + + dialog.title = qsTr("Edit Contact"); + dialog.open(); + } + + x: parent.width / 2 - width / 2 + y: parent.height / 2 - height / 2 + + focus: true + modal: true + title: qsTr("Add Contact") + standardButtons: Dialog.Ok | Dialog.Cancel + + contentItem: ContactForm { + id: form + } + + onAccepted: finished(form.fullName.text, form.address.text, form.city.text, form.number.text) +} diff --git a/examples/quickcontrols/contactslist/Contact/ContactForm.ui.qml b/examples/quickcontrols/contactslist/Contact/ContactForm.ui.qml new file mode 100644 index 00000000..56c91861 --- /dev/null +++ b/examples/quickcontrols/contactslist/Contact/ContactForm.ui.qml @@ -0,0 +1,72 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +import QtQuick +import QtQuick.Layouts +import QtQuick.Controls + +GridLayout { + id: grid + property alias fullName: fullName + property alias address: address + property alias city: city + property alias number: number + property int minimumInputSize: 120 + property string placeholderText: qsTr("") + + rows: 4 + columns: 2 + + Label { + text: qsTr("Full Name") + Layout.alignment: Qt.AlignLeft | Qt.AlignBaseline + } + + TextField { + id: fullName + focus: true + Layout.fillWidth: true + Layout.minimumWidth: grid.minimumInputSize + Layout.alignment: Qt.AlignLeft | Qt.AlignBaseline + placeholderText: grid.placeholderText + } + + Label { + text: qsTr("Address") + Layout.alignment: Qt.AlignLeft | Qt.AlignBaseline + } + + TextField { + id: address + Layout.fillWidth: true + Layout.minimumWidth: grid.minimumInputSize + Layout.alignment: Qt.AlignLeft | Qt.AlignBaseline + placeholderText: grid.placeholderText + } + + Label { + text: qsTr("City") + Layout.alignment: Qt.AlignLeft | Qt.AlignBaseline + } + + TextField { + id: city + Layout.fillWidth: true + Layout.minimumWidth: grid.minimumInputSize + Layout.alignment: Qt.AlignLeft | Qt.AlignBaseline + placeholderText: grid.placeholderText + } + + Label { + text: qsTr("Number") + Layout.alignment: Qt.AlignLeft | Qt.AlignBaseline + } + + TextField { + id: number + Layout.fillWidth: true + Layout.minimumWidth: grid.minimumInputSize + Layout.alignment: Qt.AlignLeft | Qt.AlignBaseline + placeholderText: grid.placeholderText + } +} diff --git a/examples/quickcontrols/contactslist/Contact/ContactList.qml b/examples/quickcontrols/contactslist/Contact/ContactList.qml new file mode 100644 index 00000000..0b7af32b --- /dev/null +++ b/examples/quickcontrols/contactslist/Contact/ContactList.qml @@ -0,0 +1,70 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +import QtQuick +import QtQuick.Controls + +ApplicationWindow { + id: window + + property int currentContact: -1 + + width: 320 + height: 480 + visible: true + title: qsTr("Contact List") + + ContactDialog { + id: contactDialog + onFinished: function(fullName, address, city, number) { + if (currentContact == -1) + contactView.model.append(fullName, address, city, number) + else + contactView.model.set(currentContact, fullName, address, city, number) + } + } + + Menu { + id: contactMenu + x: parent.width / 2 - width / 2 + y: parent.height / 2 - height / 2 + modal: true + + Label { + padding: 10 + font.bold: true + width: parent.width + horizontalAlignment: Qt.AlignHCenter + text: currentContact >= 0 ? contactView.model.get(currentContact).fullName : "" + } + MenuItem { + text: qsTr("Edit...") + onTriggered: contactDialog.editContact(contactView.model.get(currentContact)) + } + MenuItem { + text: qsTr("Remove") + onTriggered: contactView.model.remove(currentContact) + } + } + + ContactView { + id: contactView + anchors.fill: parent + onPressAndHold: { + currentContact = index + contactMenu.open() + } + } + + RoundButton { + text: qsTr("+") + highlighted: true + anchors.margins: 10 + anchors.right: parent.right + anchors.bottom: parent.bottom + onClicked: { + currentContact = -1 + contactDialog.createContact() + } + } +} diff --git a/examples/quickcontrols/contactslist/Contact/ContactView.ui.qml b/examples/quickcontrols/contactslist/Contact/ContactView.ui.qml new file mode 100644 index 00000000..3b82b681 --- /dev/null +++ b/examples/quickcontrols/contactslist/Contact/ContactView.ui.qml @@ -0,0 +1,36 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +import QtQuick +import QtQuick.Controls +import Backend + +ListView { + id: listView + + signal pressAndHold(int index) + + width: 320 + height: 480 + + focus: true + boundsBehavior: Flickable.StopAtBounds + + section.property: "fullName" + section.criteria: ViewSection.FirstCharacter + section.delegate: SectionDelegate { + width: listView.width + } + + delegate: ContactDelegate { + id: delegate + width: listView.width + onPressAndHold: listView.pressAndHold(index) + } + + model: ContactModel { + id: contactModel + } + + ScrollBar.vertical: ScrollBar { } +} diff --git a/examples/quickcontrols/contactslist/Contact/SectionDelegate.ui.qml b/examples/quickcontrols/contactslist/Contact/SectionDelegate.ui.qml new file mode 100644 index 00000000..3a62409a --- /dev/null +++ b/examples/quickcontrols/contactslist/Contact/SectionDelegate.ui.qml @@ -0,0 +1,17 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +import QtQuick +import QtQuick.Controls + +ToolBar { + id: background + + Label { + id: label + text: section + anchors.fill: parent + horizontalAlignment: Qt.AlignHCenter + verticalAlignment: Qt.AlignVCenter + } +} diff --git a/examples/quickcontrols/contactslist/Contact/qmldir b/examples/quickcontrols/contactslist/Contact/qmldir new file mode 100644 index 00000000..339d45a1 --- /dev/null +++ b/examples/quickcontrols/contactslist/Contact/qmldir @@ -0,0 +1,7 @@ +module Contact +ContactList 1.0 ContactList.qml +ContactDialog 1.0 ContactDialog.qml +ContactDelegate 1.0 ContactDelegate.ui.qml +ContactForm 1.0 ContactForm.ui.qml +ContactView 1.0 ContactView.ui.qml +SectionDelegate 1.0 SectionDelegate.ui.qml diff --git a/examples/quickcontrols/contactslist/contactlist.pyproject b/examples/quickcontrols/contactslist/contactlist.pyproject new file mode 100644 index 00000000..75b0bd69 --- /dev/null +++ b/examples/quickcontrols/contactslist/contactlist.pyproject @@ -0,0 +1,10 @@ +{ + "files": ["main.py", + "contactmodel.py", + "Contact/ContactDialog.qml", + "Contact/ContactDelegate.ui.qml", + "Contact/ContactForm.ui.qml", + "Contact/ContactList.qml", + "Contact/ContactView.ui.qml", + "Contact/SectionDelegate.ui.qml"] +} diff --git a/examples/quickcontrols/contactslist/contactmodel.py b/examples/quickcontrols/contactslist/contactmodel.py new file mode 100644 index 00000000..5d2746c2 --- /dev/null +++ b/examples/quickcontrols/contactslist/contactmodel.py @@ -0,0 +1,116 @@ +# Copyright (C) 2023 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +import bisect +from dataclasses import dataclass +from enum import IntEnum + +from PySide6.QtCore import (QAbstractListModel, QEnum, Qt, QModelIndex, Slot, + QByteArray) +from PySide6.QtQml import QmlElement + +QML_IMPORT_NAME = "Backend" +QML_IMPORT_MAJOR_VERSION = 1 + + +@QmlElement +class ContactModel(QAbstractListModel): + + @QEnum + class ContactRole(IntEnum): + FullNameRole = Qt.DisplayRole + AddressRole = Qt.UserRole + CityRole = Qt.UserRole + 1 + NumberRole = Qt.UserRole + 2 + + @dataclass + class Contact: + fullName: str + address: str + city: str + number: str + + def __init__(self, parent=None) -> None: + super().__init__(parent) + self.m_contacts = [] + self.m_contacts.append(self.Contact("Angel Hogan", "Chapel St. 368 ", "Clearwater", + "0311 1823993")) + self.m_contacts.append(self.Contact("Felicia Patton", "Annadale Lane 2", "Knoxville", + "0368 1244494")) + self.m_contacts.append(self.Contact("Grant Crawford", "Windsor Drive 34", "Riverdale", + "0351 7826892")) + self.m_contacts.append(self.Contact("Gretchen Little", "Sunset Drive 348", "Virginia Beach", + "0343 1234991")) + self.m_contacts.append(self.Contact("Geoffrey Richards", "University Lane 54", "Trussville", + "0423 2144944")) + self.m_contacts.append(self.Contact("Henrietta Chavez", "Via Volto San Luca 3", + "Piobesi Torinese", "0399 2826994")) + self.m_contacts.append(self.Contact("Harvey Chandler", "North Squaw Creek 11", + "Madisonville", "0343 1244492")) + self.m_contacts.append(self.Contact("Miguel Gomez", "Wild Rose Street 13", "Trussville", + "0343 9826996")) + self.m_contacts.append(self.Contact("Norma Rodriguez", " Glen Eagles Street 53", + "Buffalo", "0241 5826596")) + self.m_contacts.append(self.Contact("Shelia Ramirez", "East Miller Ave 68", "Pickerington", + "0346 4844556")) + self.m_contacts.append(self.Contact("Stephanie Moss", "Piazza Trieste e Trento 77", + "Roata Chiusani", "0363 0510490")) + + def rowCount(self, parent=QModelIndex()): + return len(self.m_contacts) + + def data(self, index: QModelIndex, role: int): + row = index.row() + if row < self.rowCount(): + if role == ContactModel.ContactRole.FullNameRole: + return self.m_contacts[row].fullName + elif role == ContactModel.ContactRole.AddressRole: + return self.m_contacts[row].address + elif role == ContactModel.ContactRole.CityRole: + return self.m_contacts[row].city + elif role == ContactModel.ContactRole.NumberRole: + return self.m_contacts[row].number + + def roleNames(self): + default = super().roleNames() + default[ContactModel.ContactRole.FullNameRole] = QByteArray(b"fullName") + default[ContactModel.ContactRole.AddressRole] = QByteArray(b"address") + default[ContactModel.ContactRole.CityRole] = QByteArray(b"city") + default[ContactModel.ContactRole.NumberRole] = QByteArray(b"number") + return default + + @Slot(int) + def get(self, row: int): + contact = self.m_contacts[row] + return {"fullName": contact.fullName, "address": contact.address, + "city": contact.city, "number": contact.number} + + @Slot(str, str, str, str) + def append(self, full_name: str, address: str, city: str, number: str): + contact = self.Contact(full_name, address, city, number) + contact_names = [contact.fullName for contact in self.m_contacts] + index = bisect.bisect(contact_names, contact.fullName) + self.beginInsertRows(QModelIndex(), index, index) + self.m_contacts.insert(index, contact) + self.endInsertRows() + + @Slot(int, str, str, str, str) + def set(self, row: int, full_name: str, address: str, city: str, number: str): + if row < 0 or row >= len(self.m_contacts): + return + + self.m_contacts[row] = self.Contact(full_name, address, city, number) + self.dataChanged(self.index(row, 0), self.index(row, 0), + [ContactModel.ContactRole.FullNameRole, + ContactModel.ContactRole.AddressRole, + ContactModel.ContactRole.CityRole, + ContactModel.ContactRole.NumberRole]) + + @Slot(int) + def remove(self, row): + if row < 0 or row >= len(self.m_contacts): + return + + self.beginRemoveRows(QModelIndex(), row, row) + del self.m_contacts[row] + self.endRemoveRows() diff --git a/examples/quickcontrols/contactslist/doc/contactslist.rst b/examples/quickcontrols/contactslist/doc/contactslist.rst new file mode 100644 index 00000000..b5540e39 --- /dev/null +++ b/examples/quickcontrols/contactslist/doc/contactslist.rst @@ -0,0 +1,15 @@ +Qt Quick Controls - Contact List +================================ + +.. tags:: Android + +A QML app using Qt Quick Controls and a Python class that implements a simple +contact list. This example can also be deployed to Android using +**pyside6-android-deploy** + +A PySide6 application that demonstrates the analogous example in Qt +`ContactsList `_ + +.. image:: qtquickcontrols-contactlist.png + :width: 400 + :alt: ContactList Screenshot diff --git a/examples/quickcontrols/contactslist/doc/qtquickcontrols-contactlist.png b/examples/quickcontrols/contactslist/doc/qtquickcontrols-contactlist.png new file mode 100644 index 00000000..9f1c3065 Binary files /dev/null and b/examples/quickcontrols/contactslist/doc/qtquickcontrols-contactlist.png differ diff --git a/examples/quickcontrols/contactslist/main.py b/examples/quickcontrols/contactslist/main.py new file mode 100644 index 00000000..6c934ec8 --- /dev/null +++ b/examples/quickcontrols/contactslist/main.py @@ -0,0 +1,27 @@ +# Copyright (C) 2023 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +""" +PySide6 port of Qt Quick Controls Contact List example from Qt v6.x +""" +import sys +from pathlib import Path +from PySide6.QtGui import QGuiApplication +from PySide6.QtQml import QQmlApplicationEngine + +from contactmodel import ContactModel # noqa: F401 + +if __name__ == '__main__': + app = QGuiApplication(sys.argv) + app.setOrganizationName("QtProject") + app.setApplicationName("ContactsList") + engine = QQmlApplicationEngine() + + engine.addImportPath(Path(__file__).parent) + engine.loadFromModule("Contact", "ContactList") + + if not engine.rootObjects(): + sys.exit(-1) + + del engine + sys.exit(app.exec()) diff --git a/examples/quickcontrols/filesystemexplorer/filesystemexplorer.py b/examples/quickcontrols/filesystemexplorer/filesystemexplorer.py index 90579b36..2bc733c3 100644 --- a/examples/quickcontrols/filesystemexplorer/filesystemexplorer.py +++ b/examples/quickcontrols/filesystemexplorer/filesystemexplorer.py @@ -44,7 +44,8 @@ class FileSystemModel(QFileSystemModel): file = QFile(path) mime = self.db.mimeTypeForFile(QFileInfo(file)) - if 'text' in mime.comment().lower() or any('text' in s.lower() for s in mime.parentMimeTypes()): + if ('text' in mime.comment().lower() + or any('text' in s.lower() for s in mime.parentMimeTypes())): if file.open(QFile.ReadOnly | QFile.Text): stream = QTextStream(file).readAll() return stream diff --git a/examples/quickcontrols/gallery/doc/gallery.rst b/examples/quickcontrols/gallery/doc/gallery.rst index 29cd49f1..acd5096f 100644 --- a/examples/quickcontrols/gallery/doc/gallery.rst +++ b/examples/quickcontrols/gallery/doc/gallery.rst @@ -1,6 +1,8 @@ Qt Quick Controls 2 - Gallery ============================= +.. tags:: Android + The gallery example is a simple application with a drawer menu that contains all the Qt Quick Controls 2. Each menu item opens a page that shows the graphical appearance of a control, allows you to interact with the control, and diff --git a/examples/quickcontrols/gallery/gallery.py b/examples/quickcontrols/gallery/gallery.py index 1cdc30ba..6c2a3612 100644 --- a/examples/quickcontrols/gallery/gallery.py +++ b/examples/quickcontrols/gallery/gallery.py @@ -17,7 +17,7 @@ from PySide6.QtCore import QSettings, QUrl from PySide6.QtQml import QQmlApplicationEngine from PySide6.QtQuickControls2 import QQuickStyle -import rc_gallery +import rc_gallery # noqa: F401 if __name__ == "__main__": QGuiApplication.setApplicationName("Gallery") diff --git a/examples/serialbus/can/main.py b/examples/serialbus/can/main.py index 77d2e601..97cdcc90 100644 --- a/examples/serialbus/can/main.py +++ b/examples/serialbus/can/main.py @@ -16,4 +16,3 @@ if __name__ == "__main__": w = MainWindow() w.show() sys.exit(QCoreApplication.exec()) - diff --git a/examples/serialbus/can/mainwindow.py b/examples/serialbus/can/mainwindow.py index e5d068e0..ae2a08f1 100644 --- a/examples/serialbus/can/mainwindow.py +++ b/examples/serialbus/can/mainwindow.py @@ -74,7 +74,7 @@ class MainWindow(QMainWindow): self.m_ui.actionDisconnect.triggered.connect(self.disconnect_device) self.m_ui.actionResetController.triggered.connect(self._reset_controller) self.m_ui.actionQuit.triggered.connect(self.close) - self.m_ui.actionAboutQt.triggered.connect(qApp.aboutQt) + self.m_ui.actionAboutQt.triggered.connect(qApp.aboutQt) # noqa: F821 self.m_ui.actionClearLog.triggered.connect(self.m_model.clear) self.m_ui.actionPluginDocumentation.triggered.connect(show_help) self.m_ui.actionDeviceInformation.triggered.connect(self._action_device_information) @@ -109,9 +109,11 @@ class MainWindow(QMainWindow): else: self.m_model.set_queue_limit(0) - device, error_string = QCanBus.instance().createDevice(p.plugin_name, p.device_interface_name) + device, error_string = QCanBus.instance().createDevice( + p.plugin_name, p.device_interface_name) if not device: - self.m_status.setText(f"Error creating device '{p.plugin_name}', reason: '{error_string}'") + self.m_status.setText( + f"Error creating device '{p.plugin_name}', reason: '{error_string}'") return self.m_number_frames_written = 0 @@ -136,18 +138,22 @@ class MainWindow(QMainWindow): config_bit_rate = self.m_can_device.configurationParameter(QCanBusDevice.BitRateKey) if config_bit_rate > 0: is_can_fd = bool(self.m_can_device.configurationParameter(QCanBusDevice.CanFdKey)) - config_data_bit_rate = self.m_can_device.configurationParameter(QCanBusDevice.DataBitRateKey) + config_data_bit_rate = self.m_can_device.configurationParameter( + QCanBusDevice.DataBitRateKey) bit_rate = config_bit_rate / 1000 if is_can_fd and config_data_bit_rate > 0: data_bit_rate = config_data_bit_rate / 1000 - m = f"Plugin: {p.plugin_name}, connected to {p.device_interface_name} at {bit_rate} / {data_bit_rate} kBit/s" + m = (f"Plugin: {p.plugin_name}, connected to {p.device_interface_name} " + f"at {bit_rate} / {data_bit_rate} kBit/s") self.m_status.setText(m) else: - m = f"Plugin: {p.plugin_name}, connected to {p.device_interface_name} at {bit_rate} kBit/s" + m = (f"Plugin: {p.plugin_name}, connected to {p.device_interface_name} " + f"at {bit_rate} kBit/s") self.m_status.setText(m) else: - self.m_status.setText(f"Plugin: {p.plugin_name}, connected to {p.device_interface_name}") + self.m_status.setText( + f"Plugin: {p.plugin_name}, connected to {p.device_interface_name}") if self.m_can_device.hasBusStatus(): self.m_busStatusTimer.start(2000) diff --git a/examples/serialbus/can/sendframebox.py b/examples/serialbus/can/sendframebox.py index 99e4ab7c..6472fc47 100644 --- a/examples/serialbus/can/sendframebox.py +++ b/examples/serialbus/can/sendframebox.py @@ -157,8 +157,8 @@ class SendFrameBox(QGroupBox): @Slot(bool) def _flexible_datarate(self, value): - l = MAX_PAYLOAD_FD if value else MAX_PAYLOAD - self.m_hexStringValidator.set_max_length(l) + len = MAX_PAYLOAD_FD if value else MAX_PAYLOAD + self.m_hexStringValidator.set_max_length(len) self.m_ui.bitrateSwitchBox.setEnabled(value) if not value: self.m_ui.bitrateSwitchBox.setChecked(False) diff --git a/examples/serialbus/modbus/modbusclient/mainwindow.py b/examples/serialbus/modbus/modbusclient/mainwindow.py index 80a336c5..02f9d478 100644 --- a/examples/serialbus/modbus/modbusclient/mainwindow.py +++ b/examples/serialbus/modbus/modbusclient/mainwindow.py @@ -316,7 +316,7 @@ class MainWindow(QMainWindow): # do not go beyond 10 entries number_of_entries = min(int(self.ui.readSize.currentText()), - 10 - start_address) + 10 - start_address) return QModbusDataUnit(table, start_address, number_of_entries) def write_request(self): @@ -327,5 +327,5 @@ class MainWindow(QMainWindow): # do not go beyond 10 entries number_of_entries = min(int(self.ui.writeSize.currentText()), - 10 - start_address) + 10 - start_address) return QModbusDataUnit(table, start_address, number_of_entries) diff --git a/examples/serialport/terminal/mainwindow.py b/examples/serialport/terminal/mainwindow.py index c35ed285..bdfb0fb0 100644 --- a/examples/serialport/terminal/mainwindow.py +++ b/examples/serialport/terminal/mainwindow.py @@ -47,7 +47,7 @@ class MainWindow(QMainWindow): self.m_ui.actionConfigure.triggered.connect(self.m_settings.show) self.m_ui.actionClear.triggered.connect(self.m_console.clear) self.m_ui.actionAbout.triggered.connect(self.about) - self.m_ui.actionAboutQt.triggered.connect(qApp.aboutQt) + self.m_ui.actionAboutQt.triggered.connect(qApp.aboutQt) # noqa: F821 self.m_serial.errorOccurred.connect(self.handle_error) self.m_serial.readyRead.connect(self.read_data) diff --git a/examples/serialport/terminal/settingsdialog.py b/examples/serialport/terminal/settingsdialog.py index 07034e4a..c9373d5b 100644 --- a/examples/serialport/terminal/settingsdialog.py +++ b/examples/serialport/terminal/settingsdialog.py @@ -29,7 +29,7 @@ class Settings(): self.string_data_bits = "" self.parity = QSerialPort.NoParity self.string_parity = "" - self.stop_bits = QSerialPort.OneStop + self.stop_bits = QSerialPort.OneStop self.string_stop_bits = "" self.flow_control = QSerialPort.SoftwareControl self.string_flow_control = "" @@ -52,7 +52,8 @@ class SettingsDialog(QDialog): self.m_ui.applyButton.clicked.connect(self.apply) self.m_ui.serialPortInfoListBox.currentIndexChanged.connect(self.show_port_info) self.m_ui.baudRateBox.currentIndexChanged.connect(self.check_custom_baud_rate_policy) - self.m_ui.serialPortInfoListBox.currentIndexChanged.connect(self.check_custom_device_path_policy) + self.m_ui.serialPortInfoListBox.currentIndexChanged.connect( + self.check_custom_device_path_policy) self.fill_ports_parameters() self.fill_ports_info() diff --git a/examples/spatialaudio/audiopanning/main.py b/examples/spatialaudio/audiopanning/main.py index 55770e3b..19b6c3bb 100644 --- a/examples/spatialaudio/audiopanning/main.py +++ b/examples/spatialaudio/audiopanning/main.py @@ -149,7 +149,7 @@ class AudioWidget(QWidget): self._file_dialog = QFileDialog(self, "Open Audio File", directory) self._file_dialog.setAcceptMode(QFileDialog.AcceptOpen) mime_types = ["audio/mpeg", "audio/aac", "audio/x-ms-wma", - "audio/x-flac+ogg", "audio/x-wav"] + "audio/x-flac+ogg", "audio/x-wav"] self._file_dialog.setMimeTypeFilters(mime_types) self._file_dialog.selectMimeTypeFilter(mime_types[0]) diff --git a/examples/sql/books/bookdelegate.py b/examples/sql/books/bookdelegate.py index 1d1dba86..78295adf 100644 --- a/examples/sql/books/bookdelegate.py +++ b/examples/sql/books/bookdelegate.py @@ -13,7 +13,8 @@ class BookDelegate(QSqlRelationalDelegate): def __init__(self, parent=None): QSqlRelationalDelegate.__init__(self, parent) - self.star = QPixmap(":/images/star.png") + self.star = QPixmap(":/images/star.svg") + self.star_filled = QPixmap(":/images/star-filled.svg") def paint(self, painter, option, index): """ Paint the items in the table. @@ -43,17 +44,19 @@ class BookDelegate(QSqlRelationalDelegate): if option.state & QStyle.State_Selected: painter.fillRect(option.rect, - option.palette.color(color_group, QPalette.Highlight)) + option.palette.color(color_group, QPalette.Highlight)) rating = model.data(index, Qt.DisplayRole) width = self.star.width() height = self.star.height() x = option.rect.x() y = option.rect.y() + (option.rect.height() / 2) - (height / 2) - for i in range(rating): - painter.drawPixmap(x, y, self.star) + for i in range(5): + if i < rating: + painter.drawPixmap(x, y, self.star_filled) + else: + painter.drawPixmap(x, y, self.star) x += width - pen = painter.pen() painter.setPen(option.palette.color(QPalette.Mid)) painter.drawLine(option.rect.bottomLeft(), option.rect.bottomRight()) diff --git a/examples/sql/books/books.qrc b/examples/sql/books/books.qrc index d6ad2133..a52ee381 100644 --- a/examples/sql/books/books.qrc +++ b/examples/sql/books/books.qrc @@ -1,5 +1,6 @@ - images/star.png + images/star.svg + images/star-filled.svg diff --git a/examples/sql/books/bookwindow.py b/examples/sql/books/bookwindow.py index 4acb63d4..7f9e0f94 100644 --- a/examples/sql/books/bookwindow.py +++ b/examples/sql/books/bookwindow.py @@ -2,7 +2,7 @@ # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause from PySide6.QtWidgets import (QAbstractItemView, QDataWidgetMapper, - QHeaderView, QMainWindow, QMessageBox) + QHeaderView, QMainWindow, QMessageBox) from PySide6.QtGui import QKeySequence from PySide6.QtSql import QSqlRelation, QSqlRelationalTableModel, QSqlTableModel from PySide6.QtCore import Qt, Slot @@ -58,7 +58,7 @@ class BookWindow(QMainWindow, Ui_BookWindow): # Lock and prohibit resizing of the width of the rating column: self.bookTable.horizontalHeader().setSectionResizeMode(model.fieldIndex("rating"), - QHeaderView.ResizeToContents) + QHeaderView.ResizeToContents) mapper = QDataWidgetMapper(self) mapper.setModel(model) @@ -77,22 +77,22 @@ class BookWindow(QMainWindow, Ui_BookWindow): def showError(self, err): QMessageBox.critical(self, "Unable to initialize Database", - f"Error initializing database: {err.text()}") + f"Error initializing database: {err.text()}") def create_menubar(self): file_menu = self.menuBar().addMenu(self.tr("&File")) quit_action = file_menu.addAction(self.tr("&Quit")) - quit_action.triggered.connect(qApp.quit) + quit_action.triggered.connect(qApp.quit) # noqa: F821 help_menu = self.menuBar().addMenu(self.tr("&Help")) about_action = help_menu.addAction(self.tr("&About")) about_action.setShortcut(QKeySequence.HelpContents) about_action.triggered.connect(self.about) aboutQt_action = help_menu.addAction("&About Qt") - aboutQt_action.triggered.connect(qApp.aboutQt) + aboutQt_action.triggered.connect(qApp.aboutQt) # noqa: F821 @Slot() def about(self): QMessageBox.about(self, self.tr("About Books"), - self.tr("

The Books example shows how to use Qt SQL classes " - "with a model/view framework.")) + self.tr("

The Books example shows how to use Qt SQL classes " + "with a model/view framework.")) diff --git a/examples/sql/books/images/star-filled.svg b/examples/sql/books/images/star-filled.svg new file mode 100644 index 00000000..8a2aee27 --- /dev/null +++ b/examples/sql/books/images/star-filled.svg @@ -0,0 +1 @@ + diff --git a/examples/sql/books/images/star.png b/examples/sql/books/images/star.png deleted file mode 100644 index 87f4464b..00000000 Binary files a/examples/sql/books/images/star.png and /dev/null differ diff --git a/examples/sql/books/images/star.svg b/examples/sql/books/images/star.svg new file mode 100644 index 00000000..d959abc1 --- /dev/null +++ b/examples/sql/books/images/star.svg @@ -0,0 +1 @@ + diff --git a/examples/sql/books/main.py b/examples/sql/books/main.py index 9a6575dc..025b5588 100644 --- a/examples/sql/books/main.py +++ b/examples/sql/books/main.py @@ -4,7 +4,7 @@ import sys from PySide6.QtWidgets import QApplication from bookwindow import BookWindow -import rc_books +import rc_books # noqa: F401 if __name__ == "__main__": app = QApplication([]) diff --git a/examples/sql/books/rc_books.py b/examples/sql/books/rc_books.py index b589380e..123d50de 100644 --- a/examples/sql/books/rc_books.py +++ b/examples/sql/books/rc_books.py @@ -1,62 +1,86 @@ # Resource object code (Python 3) # Created by: object code -# Created by: The Resource Compiler for Qt version 6.2.2 +# Created by: The Resource Compiler for Qt version 6.6.1 # WARNING! All changes made in this file will be lost! from PySide6 import QtCore qt_resource_data = b"\ -\x00\x00\x03\x0e\ -\x89\ -PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\ -\x00\x00\x10\x00\x00\x00\x10\x08\x06\x00\x00\x00\x1f\xf3\xffa\ -\x00\x00\x00\x09pHYs\x00\x00\x0b\x11\x00\x00\x0b\x11\ -\x01\x7fd_\x91\x00\x00\x00\x07tIME\x07\xd4\x09\ -\x03\x12\x11\x08\x18~\xe5:\x00\x00\x00\x06bKGD\ -\x00\xff\x00\xff\x00\xff\xa0\xbd\xa7\x93\x00\x00\x02\x9bID\ -AT8\xcbc\x98:c\x1e#:\xe6\xe5d\xcf\x17\ -\x12\x12\x16\xc4&\x87\x8e\x19\xb0\x09v\xc6\x18\xb7x\xea\ -\x8b\xcd\x9c=o\x09i\x06,X4\x8f\xf1\xd2\xa5\x99\ -L\xb9\xa1\x16\xc5\xc7\xbb\xed\xff\x0a\xf2\xb2;M\x9f\xb5\ -\x908\x03\x16,\x9a\xcb\xf8\xe0\xde\x04\x96\xc7\x0f\xdby\ -\xe7MO\xc8\xfbv\xbf\xe5\xff\xb4\x0a\x9b\x9by\x851\ -\xdc\xd3g-\x82k\x983\x7f)\xe3l F1`\ -\xca\xf4y\x8c\xd7\xaeMg\x02i~\xf2\xa8Y\xe1\xd2\ -\xa5\xfa\xdc_\x9f7\xfd\xffx\xbf\xea\x7fE\x96m\x97\ -\x81\x81>'33\x8b\xa5\x9e8gi\xb8\x9e\xc0f\ -&&\xa6D\x14\x03&N\x9d\xc7x\xef\xdeD\x96'\ -\x0f[E\x9f>j\xd6\xbdu\xb3\x22\xef\xd7\xb7=\xff\ -\xbe\x7f\xe8\xfb\x7f~S\xcc\xef\x05\xc5\xea\x9fNOQ\ -\xfb\x7f\xbaM\xed\xbf\x87\x1a\xefn5-\x1dV\x14\x03\ -f\xcf[\xce\xa8\xa4\xa9![W\xed\x9b}\xefJ\xcb\ -\xcew\xaf&\x7f\xfa\xfee\xc9\xff\xef\x1f\xfa\xff\xbf\xbf\ -\x95\xf2\xff\xc9^\x83\xffW\x17\xaa\xfdot\x12{\xc4\ -\xc7\xc7/\x8e\x12\x06Y\xb9\x85\xcc\xb2\x82\x1c\xf3\xa7D\ -\xab\xfe\xfa\xbe%\xe2\xff\x8fgm\xff\x7f|\x9a\x08\xd6\ -\xfc\xf5Y\xcd\xff\xd7\xe7\xfc\xfe\xdf\xde\xa0\xf5\x7fE\x94\ -\xecO\x16\x16V\xebi3\xe7\xa3\x06\xe2\xe4is\x18\ -\xe7/Z\xc1\xc8\xce\xc1i\x10\xe5\xa8\xd2\xbe\xa6\xcd\xe7\ -\xf6\xc3m\x99\xff~^(\xf8\xff\xe1j\xe0\xff\x17G\ -L\xff\xdf\xdf\xae\xf6\xbf\xc2]\xf4\xba\x9a\x9a\x06\x1bF\ -,\x00\x01#2\xe6\xe6\xe6`Q\xd6\x941_\xde\xe4\ -q\xfb\xc3y\xd3\xff\x1b\x8aT\xff\xbf?`\xff\xff\xdc\ -l\xe5\xff\xea\xc2\x1c9\xd3g-\xc0i\x00\x13\x10\x8b\ -\x03\xb1?\x10\xe7\xf5\x16\xd8\xde\xf8p\xc6\xe4\xbf\x9d<\ -\xf7t\x7fC\xe9\x95\xb7\x96\xd9\xff\x9b\x9c,\xfdN@\ -@H\x14\x9b\x01LP,\x06\xc4\x19@|\x22;\xca\ -\xf0\xe7\xe9\xf9\x06\xff\x81\xec\x03@\xbc^\x82\x9f\xf3\xf6\ -\x9e\x1a\xf3_az\x823P\xd2\x01T#3\x10\x0b\ -\x00\xb1\x1e\x10\x17\x03\xf1\xd1\xa8@\xdd\x9f\xad\x09J \ -\x03\xfe\x00\xf17 >\x0f\xb4kf\xb9\xa7\xea\x0d}\ -i>#d\x03\xb4\x808\x08\x88k\x81x\x09\xd4\xc6\ -\x1b\x11a\x06\xdf\xec\x94\xb8\xdf\x03\xd9;\x81x\x1a\x10\ -\xf7\x82\xd4\xb0\xb2\xb1G\xf9\xda\x99:L\x9d9\x9f\x09\ -f\x80\x0e\x10;\x02\xb1\x13\x10[\x00\xb1\x01\x10\x07\x06\ -{h\x9c\x02\xd2k\xa0\x86\x8b\x001\x17\x10\xf3\x80\xb0\ -\x88\x88(\xcb,hFC\xf6\x02\x08\xb3\x001+\x10\ -K122\xe4\x01i7 \x96\x01b6\xa88\x0b\ -T=cW\xef$\xb0\x01\x00\xceo{\xf5UL\xf0\ -\xac\x00\x00\x00\x00IEND\xaeB`\x82\ +\x00\x00\x02e\ +<\ +svg xmlns=\x22http:\ +//www.w3.org/200\ +0/svg\x22 width=\x2224\ +\x22 height=\x2224\x22 fi\ +ll=\x22#0d0d0d\x22>\x0a\ +\x00\x00\x01\xfa\ +<\ +svg xmlns=\x22http:\ +//www.w3.org/200\ +0/svg\x22 width=\x2224\ +\x22 height=\x2224\x22 fi\ +ll=\x22none\x22>\x0a\ " qt_resource_name = b"\ @@ -64,19 +88,25 @@ qt_resource_name = b"\ \x07\x03}\xc3\ \x00i\ \x00m\x00a\x00g\x00e\x00s\ +\x00\x0f\ +\x02\x11 \x07\ +\x00s\ +\x00t\x00a\x00r\x00-\x00f\x00i\x00l\x00l\x00e\x00d\x00.\x00s\x00v\x00g\ \x00\x08\ -\x0a\x85X\x07\ +\x0a\x85U\x87\ \x00s\ -\x00t\x00a\x00r\x00.\x00p\x00n\x00g\ +\x00t\x00a\x00r\x00.\x00s\x00v\x00g\ " qt_resource_struct = b"\ \x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x01\ \x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x02\ +\x00\x00\x00\x00\x00\x02\x00\x00\x00\x02\x00\x00\x00\x02\ \x00\x00\x00\x00\x00\x00\x00\x00\ \x00\x00\x00\x12\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\ -\x00\x00\x01z\xe7\xee&\xfd\ +\x00\x00\x01\x8c\xd4\xc79\xcf\ +\x00\x00\x006\x00\x00\x00\x00\x00\x01\x00\x00\x02i\ +\x00\x00\x01\x8c\xd4\xc79\xcf\ " def qInitResources(): diff --git a/examples/statemachine/rogue/rogue.py b/examples/statemachine/rogue/rogue.py index a43d4d1b..f0ce9c28 100644 --- a/examples/statemachine/rogue/rogue.py +++ b/examples/statemachine/rogue/rogue.py @@ -18,11 +18,11 @@ class MovementTransition(QEventTransition): self.window = window def eventTest(self, event): - if (event.type() == QEvent.StateMachineWrapped and - event.event().type() == QEvent.KeyPress): + if (event.type() == QEvent.StateMachineWrapped + and event.event().type() == QEvent.KeyPress): key = event.event().key() - return (key == Qt.Key_2 or key == Qt.Key_8 or - key == Qt.Key_6 or key == Qt.Key_4) + return (key == Qt.Key_2 or key == Qt.Key_8 + or key == Qt.Key_6 or key == Qt.Key_4) return False def onTransition(self, event): @@ -74,8 +74,8 @@ class MainWindow(QMainWindow): for x in range(self.width): column = [] for y in range(self.height): - if (x == 0 or x == self.width - 1 or y == 0 or - y == self.height - 1 or generator.bounded(0, 40) == 0): + if (x == 0 or x == self.width - 1 or y == 0 + or y == self.height - 1 or generator.bounded(0, 40) == 0): column.append('#') else: column.append('.') @@ -113,7 +113,7 @@ class MainWindow(QMainWindow): input_state.addTransition(quit_transition) machine.setInitialState(input_state) - machine.finished.connect(qApp.quit) + machine.finished.connect(qApp.quit) # noqa: F821 machine.start() def sizeHint(self): diff --git a/examples/utils/pyside_config.py b/examples/utils/pyside_config.py index feb56ed1..e8333e2f 100644 --- a/examples/utils/pyside_config.py +++ b/examples/utils/pyside_config.py @@ -78,10 +78,12 @@ options.append(("--shiboken-module-shared-libraries-cmake", options.append(("--pyside-shared-libraries-qmake", lambda: get_shared_libraries_qmake(Package.PYSIDE_MODULE), pyside_libs_error, - "Print paths of f{PYSIDE_MODULE} shared libraries (.so's, .dylib's, .dll's) for qmake")) + "Print paths of f{PYSIDE_MODULE} shared libraries (.so's, .dylib's, .dll's) " + "for qmake")) options.append(("--pyside-shared-libraries-cmake", lambda: get_shared_libraries_cmake(Package.PYSIDE_MODULE), pyside_libs_error, - f"Print paths of {PYSIDE_MODULE} shared libraries (.so's, .dylib's, .dll's) for cmake")) + f"Print paths of {PYSIDE_MODULE} shared libraries (.so's, .dylib's, .dll's) " + "for cmake")) options_usage = '' for i, (flag, _, _, description) in enumerate(options): @@ -153,11 +155,11 @@ def link_option(lib): # libraries when compiling the project baseName = os.path.basename(lib) link = ' -l' - if sys.platform in ['linux', 'linux2']: # Linux: 'libfoo.so' -> '/absolute/path/libfoo.so' + if sys.platform in ['linux', 'linux2']: # Linux: 'libfoo.so' -> '/absolute/path/libfoo.so' link = lib - elif sys.platform in ['darwin']: # Darwin: 'libfoo.so' -> '-lfoo' + elif sys.platform in ['darwin']: # Darwin: 'libfoo.so' -> '-lfoo' link += os.path.splitext(baseName[3:])[0] - else: # Windows: 'libfoo.dll' -> 'libfoo.dll' + else: # Windows: 'libfoo.dll' -> 'libfoo.dll' link += os.path.splitext(baseName)[0] return link @@ -212,7 +214,7 @@ def python_link_flags_qmake(): # e.g.: "Program Files" to "Progra~1" for d in libdir.split("\\"): if " " in d: - libdir = libdir.replace(d, d.split(" ")[0][:-1]+"~1") + libdir = libdir.replace(d, d.split(" ")[0][:-1] + "~1") lib_flags = flags['lib'] return f'-L{libdir} -l{lib_flags}' elif sys.platform == 'darwin': diff --git a/examples/webenginequick/nanobrowser/quicknanobrowser.py b/examples/webenginequick/nanobrowser/quicknanobrowser.py index ae4543ac..aee79c2a 100644 --- a/examples/webenginequick/nanobrowser/quicknanobrowser.py +++ b/examples/webenginequick/nanobrowser/quicknanobrowser.py @@ -14,7 +14,7 @@ from PySide6.QtQml import QQmlApplicationEngine, QmlElement, QmlSingleton from PySide6.QtGui import QGuiApplication from PySide6.QtWebEngineQuick import QtWebEngineQuick -import rc_resources +import rc_resources # noqa: F401 # To be used on the @QmlElement decorator @@ -40,8 +40,8 @@ class Utils(QObject): if __name__ == '__main__': - QCoreApplication.setApplicationName("Quick Nano Browser"); - QCoreApplication.setOrganizationName("QtProject"); + QCoreApplication.setApplicationName("Quick Nano Browser") + QCoreApplication.setOrganizationName("QtProject") QtWebEngineQuick.initialize() diff --git a/examples/webenginewidgets/markdowneditor/main.py b/examples/webenginewidgets/markdowneditor/main.py index f89aa97d..4d787f0f 100644 --- a/examples/webenginewidgets/markdowneditor/main.py +++ b/examples/webenginewidgets/markdowneditor/main.py @@ -9,7 +9,7 @@ from PySide6.QtCore import QCoreApplication from PySide6.QtWidgets import QApplication from mainwindow import MainWindow -import rc_markdowneditor +import rc_markdowneditor # noqa: F401 if __name__ == '__main__': diff --git a/examples/webenginewidgets/notifications/notificationpopup.py b/examples/webenginewidgets/notifications/notificationpopup.py index bbea8610..e68ce3d6 100644 --- a/examples/webenginewidgets/notifications/notificationpopup.py +++ b/examples/webenginewidgets/notifications/notificationpopup.py @@ -51,8 +51,8 @@ class NotificationPopup(QWidget): self.notification.closed.connect(self.onClosed) QTimer.singleShot(10000, lambda: self.onClosed()) - self.move(self.parentWidget().mapToGlobal(self.parentWidget().rect().bottomRight() - - QPoint(self.width() + 10, self.height() + 10))) + self.move(self.parentWidget().mapToGlobal(self.parentWidget().rect().bottomRight() + - QPoint(self.width() + 10, self.height() + 10))) @Slot() def onClosed(self): diff --git a/examples/webenginewidgets/simplebrowser/browser.py b/examples/webenginewidgets/simplebrowser/browser.py index 4dc65bfa..a124ea08 100644 --- a/examples/webenginewidgets/simplebrowser/browser.py +++ b/examples/webenginewidgets/simplebrowser/browser.py @@ -33,7 +33,8 @@ class Browser(QObject): s.setAttribute(QWebEngineSettings.DnsPrefetchEnabled, True) s.setAttribute(QWebEngineSettings.LocalContentCanAccessRemoteUrls, True) s.setAttribute(QWebEngineSettings.LocalContentCanAccessFileUrls, False) - self._profile.downloadRequested.connect(self._download_manager_widget.download_requested) + self._profile.downloadRequested.connect( + self._download_manager_widget.download_requested) profile = QWebEngineProfile.defaultProfile() if offTheRecord else self._profile main_window = BrowserWindow(self, profile, False) diff --git a/examples/webenginewidgets/simplebrowser/browserwindow.py b/examples/webenginewidgets/simplebrowser/browserwindow.py index 57686574..4efdfbdd 100644 --- a/examples/webenginewidgets/simplebrowser/browserwindow.py +++ b/examples/webenginewidgets/simplebrowser/browserwindow.py @@ -84,7 +84,8 @@ class BrowserWindow(QMainWindow): if not forDevTools: self._tab_widget.link_hovered.connect(self._show_status_message) self._tab_widget.load_progress.connect(self.handle_web_view_load_progress) - self._tab_widget.web_action_enabled_changed.connect(self.handle_web_action_enabled_changed) + self._tab_widget.web_action_enabled_changed.connect( + self.handle_web_action_enabled_changed) self._tab_widget.url_changed.connect(self._url_changed) self._tab_widget.fav_icon_changed.connect(self._fav_action.setIcon) self._tab_widget.dev_tools_requested.connect(self.handle_dev_tools_requested) @@ -328,7 +329,7 @@ class BrowserWindow(QMainWindow): def create_help_menu(self): help_menu = QMenu("Help") - help_menu.addAction("About Qt", qApp.aboutQt) + help_menu.addAction("About Qt", qApp.aboutQt) # noqa: F821 return help_menu @Slot() diff --git a/examples/webenginewidgets/simplebrowser/downloadwidget.py b/examples/webenginewidgets/simplebrowser/downloadwidget.py index 89dc2889..6f3dbf78 100644 --- a/examples/webenginewidgets/simplebrowser/downloadwidget.py +++ b/examples/webenginewidgets/simplebrowser/downloadwidget.py @@ -25,7 +25,6 @@ def with_unit(bytes): class DownloadWidget(QFrame): """Displays one ongoing or finished download (QWebEngineDownloadRequest).""" - # This signal is emitted when the user indicates that they want to remove # this download from the downloads list. remove_clicked = Signal(QWidget) diff --git a/examples/webenginewidgets/simplebrowser/main.py b/examples/webenginewidgets/simplebrowser/main.py index 8d0946cc..781ec29e 100644 --- a/examples/webenginewidgets/simplebrowser/main.py +++ b/examples/webenginewidgets/simplebrowser/main.py @@ -13,7 +13,7 @@ from PySide6.QtCore import QCoreApplication, QLoggingCategory, QUrl from browser import Browser -import data.rc_simplebrowser +import data.rc_simplebrowser # noqa: F401 if __name__ == "__main__": parser = ArgumentParser(description="Qt Widgets Web Browser", diff --git a/examples/webenginewidgets/simplebrowser/webview.py b/examples/webenginewidgets/simplebrowser/webview.py index 05932eec..84d24833 100644 --- a/examples/webenginewidgets/simplebrowser/webview.py +++ b/examples/webenginewidgets/simplebrowser/webview.py @@ -99,8 +99,10 @@ class WebView(QWebEngineView): old_page.createCertificateErrorDialog.disconnect(self.handle_certificate_error) old_page.authenticationRequired.disconnect(self.handle_authentication_required) old_page.featurePermissionRequested.disconnect(self.handle_feature_permission_requested) - old_page.proxyAuthenticationRequired.disconnect(self.handle_proxy_authentication_required) - old_page.registerProtocolHandlerRequested.disconnect(self.handle_register_protocol_handler_requested) + old_page.proxyAuthenticationRequired.disconnect( + self.handle_proxy_authentication_required) + old_page.registerProtocolHandlerRequested.disconnect( + self.handle_register_protocol_handler_requested) old_page.fileSystemAccessRequested.disconnect(self.handle_file_system_access_requested) self.create_web_action_trigger(page, QWebEnginePage.Forward) @@ -112,7 +114,8 @@ class WebView(QWebEngineView): page.authenticationRequired.connect(self.handle_authentication_required) page.featurePermissionRequested.connect(self.handle_feature_permission_requested) page.proxyAuthenticationRequired.connect(self.handle_proxy_authentication_required) - page.registerProtocolHandlerRequested.connect(self.handle_register_protocol_handler_requested) + page.registerProtocolHandlerRequested.connect( + self.handle_register_protocol_handler_requested) page.fileSystemAccessRequested.connect(self.handle_file_system_access_requested) def load_progress(self): @@ -229,8 +232,7 @@ class WebView(QWebEngineView): question = question_for_feature(feature).replace("%1", host) w = self.window() page = self.page() - if (question - and QMessageBox.question(w, title, question) == QMessageBox.Yes): + if question and QMessageBox.question(w, title, question) == QMessageBox.Yes: page.setFeaturePermission(securityOrigin, feature, QWebEnginePage.PermissionGrantedByUser) else: diff --git a/examples/webenginewidgets/widgetsnanobrowser/widgetsnanobrowser.py b/examples/webenginewidgets/widgetsnanobrowser/widgetsnanobrowser.py index 48f60c6a..2db86599 100644 --- a/examples/webenginewidgets/widgetsnanobrowser/widgetsnanobrowser.py +++ b/examples/webenginewidgets/widgetsnanobrowser/widgetsnanobrowser.py @@ -7,7 +7,7 @@ import sys from PySide6.QtCore import QUrl, Slot from PySide6.QtGui import QIcon from PySide6.QtWidgets import (QApplication, QLineEdit, - QMainWindow, QPushButton, QToolBar) + QMainWindow, QPushButton, QToolBar) from PySide6.QtWebEngineCore import QWebEnginePage from PySide6.QtWebEngineWidgets import QWebEngineView diff --git a/examples/widgets/animation/animatedtiles/animatedtiles.py b/examples/widgets/animation/animatedtiles/animatedtiles.py index b621ec11..02fc75bf 100644 --- a/examples/widgets/animation/animatedtiles/animatedtiles.py +++ b/examples/widgets/animation/animatedtiles/animatedtiles.py @@ -15,7 +15,7 @@ from PySide6.QtWidgets import (QApplication, QGraphicsItem, QGraphicsPixmapItem, QGraphicsWidget, QStyle) from PySide6.QtStateMachine import QState, QStateMachine -import animatedtiles_rc +import animatedtiles_rc # noqa: F401 # Deriving from more than one wrapped class is not supported, so we use @@ -95,7 +95,7 @@ class Button(QGraphicsWidget): painter.drawEllipse(r.adjusted(5, 5, -5, -5)) painter.drawPixmap(-self._pix.width() / 2, -self._pix.height() / 2, - self._pix) + self._pix) def mousePressEvent(self, ev): self.pressed.emit() @@ -123,7 +123,7 @@ if __name__ == '__main__': for i in range(64): item = Pixmap(kinetic_pix) item.pixmap_item.setOffset(-kinetic_pix.width() / 2, - -kinetic_pix.height() / 2) + -kinetic_pix.height() / 2) item.pixmap_item.setZValue(i) items.append(item) scene.addItem(item.pixmap_item) @@ -161,23 +161,25 @@ if __name__ == '__main__': for i, item in enumerate(items): # Ellipse. ellipse_state.assignProperty(item, 'pos', - QPointF(math.cos((i / 63.0) * 6.28) * 250, - math.sin((i / 63.0) * 6.28) * 250)) + QPointF(math.cos((i / 63.0) * 6.28) * 250, + math.sin((i / 63.0) * 6.28) * 250)) # Figure 8. figure_8state.assignProperty(item, 'pos', - QPointF(math.sin((i / 63.0) * 6.28) * 250, - math.sin(((i * 2) / 63.0) * 6.28) * 250)) + QPointF(math.sin((i / 63.0) * 6.28) * 250, + math.sin(((i * 2) / 63.0) * 6.28) * 250)) # Random. random_state.assignProperty(item, 'pos', - QPointF(-250 + generator.bounded(0, 500), - -250 + generator.bounded(0, 500))) + QPointF(-250 + generator.bounded(0, 500), + -250 + generator.bounded(0, 500))) # Tiled. + width = kinetic_pix.width() + height = kinetic_pix.height() tiled_state.assignProperty(item, 'pos', - QPointF(((i % 8) - 4) * kinetic_pix.width() + kinetic_pix.width() / 2, - ((i // 8) - 4) * kinetic_pix.height() + kinetic_pix.height() / 2)) + QPointF(((i % 8) - 4) * width + width / 2, + ((i // 8) - 4) * height + height / 2)) # Centered. centered_state.assignProperty(item, 'pos', QPointF()) @@ -188,8 +190,7 @@ if __name__ == '__main__': view.setViewportUpdateMode(QGraphicsView.BoundingRectViewportUpdate) view.setBackgroundBrush(QBrush(bg_pix)) view.setCacheMode(QGraphicsView.CacheBackground) - view.setRenderHints( - QPainter.Antialiasing | QPainter.SmoothPixmapTransform) + view.setRenderHints(QPainter.Antialiasing | QPainter.SmoothPixmapTransform) view.show() states = QStateMachine() diff --git a/examples/widgets/animation/appchooser/appchooser.py b/examples/widgets/animation/appchooser/appchooser.py index e49f37de..61005021 100644 --- a/examples/widgets/animation/appchooser/appchooser.py +++ b/examples/widgets/animation/appchooser/appchooser.py @@ -11,7 +11,7 @@ from PySide6.QtWidgets import (QApplication, QGraphicsScene, QGraphicsView, QGraphicsWidget) from PySide6.QtStateMachine import QState, QStateMachine -import appchooser_rc +import appchooser_rc # noqa: F401 class Pixmap(QGraphicsWidget): diff --git a/examples/widgets/animation/easing/easing.py b/examples/widgets/animation/easing/easing.py index f8cba006..ba503245 100644 --- a/examples/widgets/animation/easing/easing.py +++ b/examples/widgets/animation/easing/easing.py @@ -125,7 +125,6 @@ class Window(QWidget): curve_types = [(f"QEasingCurve.{e.name}", e) for e in QEasingCurve.Type if e.value <= 40] - with QPainter(pix) as painter: for curve_name, curve_type in curve_types: @@ -144,14 +143,13 @@ class Window(QWidget): # Start point. painter.setBrush(Qt.red) - start = QPoint(y_axis, - x_axis - curve_scale * curve.valueForProgress(0)) + start = QPoint(y_axis, x_axis - curve_scale * curve.valueForProgress(0)) painter.drawRect(start.x() - 1, start.y() - 1, 3, 3) # End point. painter.setBrush(Qt.blue) end = QPoint(y_axis + curve_scale, - x_axis - curve_scale * curve.valueForProgress(1)) + x_axis - curve_scale * curve.valueForProgress(1)) painter.drawRect(end.x() - 1, end.y() - 1, 3, 3) curve_path = QPainterPath() @@ -159,7 +157,7 @@ class Window(QWidget): t = 0.0 while t <= 1.0: to = QPointF(y_axis + curve_scale * t, - x_axis - curve_scale * curve.valueForProgress(t)) + x_axis - curve_scale * curve.valueForProgress(t)) curve_path.lineTo(to) t += 1.0 / curve_scale @@ -185,14 +183,14 @@ class Window(QWidget): self._anim.setCurrentTime(0) is_elastic = (curve_type.value >= QEasingCurve.InElastic.value - and curve_type.value <= QEasingCurve.OutInElastic.value) + and curve_type.value <= QEasingCurve.OutInElastic.value) is_bounce = (curve_type.value >= QEasingCurve.InBounce.value - and curve_type.value <= QEasingCurve.OutInBounce.value) + and curve_type.value <= QEasingCurve.OutInBounce.value) self._ui.periodSpinBox.setEnabled(is_elastic) self._ui.amplitudeSpinBox.setEnabled(is_elastic or is_bounce) self._ui.overshootSpinBox.setEnabled(curve_type.value >= QEasingCurve.InBack.value - and curve_type.value <= QEasingCurve.OutInBack.value) + and curve_type.value <= QEasingCurve.OutInBack.value) def path_changed(self, index): self._anim.set_path_type(index) diff --git a/examples/widgets/animation/states/states.py b/examples/widgets/animation/states/states.py index 9b85e837..509c3a7a 100644 --- a/examples/widgets/animation/states/states.py +++ b/examples/widgets/animation/states/states.py @@ -16,7 +16,7 @@ from PySide6.QtWidgets import (QApplication, QGraphicsLinearLayout, from PySide6.QtStateMachine import QState, QStateMachine -import states_rc +import states_rc # noqa: F401 class Pixmap(QGraphicsObject): diff --git a/examples/widgets/desktop/systray/window.py b/examples/widgets/desktop/systray/window.py index c04a33e3..0b5bc39d 100644 --- a/examples/widgets/desktop/systray/window.py +++ b/examples/widgets/desktop/systray/window.py @@ -9,7 +9,7 @@ from PySide6.QtWidgets import (QCheckBox, QComboBox, QDialog, QSpinBox, QStyle, QSystemTrayIcon, QTextEdit, QVBoxLayout) -import rc_systray +import rc_systray # noqa: F401 class Window(QDialog): @@ -190,8 +190,8 @@ class Window(QDialog): self._body_label = QLabel("Body:") self._body_edit = QTextEdit() - self._body_edit.setPlainText("Don't believe me. Honestly, I don't have a clue." - "\nClick this balloon for details.") + self._body_edit.setPlainText("Don't believe me. Honestly, I don't have a clue.\n" + "Click this balloon for details.") self._show_message_button = QPushButton("Show Message") self._show_message_button.setDefault(True) @@ -222,7 +222,7 @@ class Window(QDialog): self._restore_action.triggered.connect(self.showNormal) self._quit_action = QAction("Quit", self) - self._quit_action.triggered.connect(qApp.quit) + self._quit_action.triggered.connect(qApp.quit) # noqa: F821 def create_tray_icon(self): self._tray_icon_menu = QMenu(self) diff --git a/examples/widgets/dialogs/classwizard/classwizard.py b/examples/widgets/dialogs/classwizard/classwizard.py index 296cff06..0efbde69 100644 --- a/examples/widgets/dialogs/classwizard/classwizard.py +++ b/examples/widgets/dialogs/classwizard/classwizard.py @@ -15,7 +15,7 @@ from PySide6.QtWidgets import (QApplication, QComboBox, QCheckBox, QFormLayout, from listchooser import PropertyChooser, SignalChooser -import classwizard_rc +import classwizard_rc # noqa: F401 BASE_CLASSES = ['', 'PySide6.QtCore.QObject', @@ -333,7 +333,7 @@ class OutputFilesPage(QWizardPage): def _choose_output_dir(self): directory = QFileDialog.getExistingDirectory(self, "Output Directory", - self.output_dir()) + self.output_dir()) if directory: self.set_output_dir(directory) diff --git a/examples/widgets/dialogs/classwizard/listchooser.py b/examples/widgets/dialogs/classwizard/listchooser.py index bf15be88..6bf47c6d 100644 --- a/examples/widgets/dialogs/classwizard/listchooser.py +++ b/examples/widgets/dialogs/classwizard/listchooser.py @@ -27,7 +27,7 @@ class ValidatingInputDialog(QDialog): self._lineedit = QLineEdit() self._lineedit.setClearButtonEnabled(True) re = QRegularExpression(pattern) - assert(re.isValid()) + assert re.isValid() self._validator = QRegularExpressionValidator(re, self) self._lineedit.setValidator(self._validator) self._form_layout.addRow(label, self._lineedit) diff --git a/examples/widgets/dialogs/standarddialogs/standarddialogs.py b/examples/widgets/dialogs/standarddialogs/standarddialogs.py index 8a2a4426..ef677d5a 100644 --- a/examples/widgets/dialogs/standarddialogs/standarddialogs.py +++ b/examples/widgets/dialogs/standarddialogs/standarddialogs.py @@ -235,14 +235,14 @@ class Dialog(QDialog): @Slot() def set_integer(self): i, ok = QInputDialog.getInt(self, - "QInputDialog.getInteger()", "Percentage:", 25, 0, 100, 1) + "QInputDialog.getInteger()", "Percentage:", 25, 0, 100, 1) if ok: self._integer_label.setText(f"{i}%") @Slot() def set_double(self): d, ok = QInputDialog.getDouble(self, "QInputDialog.getDouble()", - "Amount:", 37.56, -10000, 10000, 2) + "Amount:", 37.56, -10000, 10000, 2) if ok: self._double_label.setText(f"${d:g}") @@ -250,23 +250,21 @@ class Dialog(QDialog): def set_item(self): items = ("Spring", "Summer", "Fall", "Winter") - item, ok = QInputDialog.getItem(self, "QInputDialog.getItem()", - "Season:", items, 0, False) + item, ok = QInputDialog.getItem(self, "QInputDialog.getItem()", "Season:", items, 0, False) if ok and item: self._item_label.setText(item) @Slot() def set_text(self): text, ok = QInputDialog.getText(self, "QInputDialog.getText()", - "User name:", QLineEdit.Normal, - QDir.home().dirName()) + "User name:", QLineEdit.Normal, QDir.home().dirName()) if ok and text != '': self._text_label.setText(text) @Slot() def set_multiline_text(self): text, ok = QInputDialog.getMultiLineText(self, "QInputDialog::getMultiLineText()", - "Address:", "John Doe\nFreedom Street") + "Address:", "John Doe\nFreedom Street") if ok and text != '': self._multiline_text_label.setText(text) @@ -301,9 +299,8 @@ class Dialog(QDialog): options_value = self._file_options.value() options = QFileDialog.Options(options_value) | QFileDialog.ShowDirsOnly - directory = QFileDialog.getExistingDirectory(self, - "QFileDialog.getExistingDirectory()", - self._directory_label.text(), options) + directory = QFileDialog.getExistingDirectory(self, "QFileDialog.getExistingDirectory()", + self._directory_label.text(), options) if directory: self._directory_label.setText(directory) @@ -312,10 +309,9 @@ class Dialog(QDialog): options_value = self._file_options.value() options = QFileDialog.Options(options_value) - fileName, filtr = QFileDialog.getOpenFileName(self, - "QFileDialog.getOpenFileName()", - self._open_file_name_label.text(), - "All Files (*);;Text Files (*.txt)", "", options) + fileName, _ = QFileDialog.getOpenFileName(self, "QFileDialog.getOpenFileName()", + self._open_file_name_label.text(), + "All Files (*);;Text Files (*.txt)", "", options) if fileName: self._open_file_name_label.setText(fileName) @@ -324,9 +320,9 @@ class Dialog(QDialog): options_value = self._file_options.value() options = QFileDialog.Options(options_value) - files, filtr = QFileDialog.getOpenFileNames(self, - "QFileDialog.getOpenFileNames()", self._open_files_path, - "All Files (*);;Text Files (*.txt)", "", options) + files, _ = QFileDialog.getOpenFileNames(self, "QFileDialog.getOpenFileNames()", + self._open_files_path, + "All Files (*);;Text Files (*.txt)", "", options) if files: self._open_files_path = files[0] file_list = ', '.join(files) @@ -337,10 +333,9 @@ class Dialog(QDialog): options_value = self._file_options.value() options = QFileDialog.Options(options_value) - fileName, filtr = QFileDialog.getSaveFileName(self, - "QFileDialog.getSaveFileName()", - self._save_file_name_label.text(), - "All Files (*);;Text Files (*.txt)", "", options) + fileName, _ = QFileDialog.getSaveFileName(self, "QFileDialog.getSaveFileName()", + self._save_file_name_label.text(), + "All Files (*);;Text Files (*.txt)", "", options) if fileName: self._save_file_name_label.setText(fileName) diff --git a/examples/widgets/dialogs/trivialwizard/trivialwizard.py b/examples/widgets/dialogs/trivialwizard/trivialwizard.py index 0eb9fb56..2e551ae1 100644 --- a/examples/widgets/dialogs/trivialwizard/trivialwizard.py +++ b/examples/widgets/dialogs/trivialwizard/trivialwizard.py @@ -15,7 +15,7 @@ def create_intro_page(): page.setTitle("Introduction") label = QLabel("This wizard will help you register your copy of " - "Super Product Two.") + "Super Product Two.") label.setWordWrap(True) layout = QVBoxLayout(page) diff --git a/examples/widgets/draganddrop/draggabletext/draggabletext.py b/examples/widgets/draganddrop/draggabletext/draggabletext.py index 4b470cc9..6ffdbd70 100644 --- a/examples/widgets/draganddrop/draggabletext/draggabletext.py +++ b/examples/widgets/draganddrop/draggabletext/draggabletext.py @@ -2,13 +2,14 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause -"""PySide6 port of the widgets/draganddrop/draggabletext example from Qt v5.x, originating from PyQt""" +"""PySide6 port of the widgets/draganddrop/draggabletext example from Qt v5.x, + originating from PyQt""" from PySide6.QtCore import QFile, QIODevice, QMimeData, QPoint, Qt, QTextStream from PySide6.QtGui import QDrag, QPalette, QPixmap from PySide6.QtWidgets import QApplication, QFrame, QLabel, QWidget -import draggabletext_rc +import draggabletext_rc # noqa: F401 class DragLabel(QLabel): diff --git a/examples/widgets/effects/lighting/lighting.py b/examples/widgets/effects/lighting/lighting.py index fe200357..f074813c 100644 --- a/examples/widgets/effects/lighting/lighting.py +++ b/examples/widgets/effects/lighting/lighting.py @@ -37,8 +37,7 @@ class Lighting(QGraphicsView): def setup_scene(self): self.m_scene.setSceneRect(-300, -200, 600, 460) - linear_grad = QLinearGradient(QPointF(-100, -100), - QPointF(100, 100)) + linear_grad = QLinearGradient(QPointF(-100, -100), QPointF(100, 100)) linear_grad.setColorAt(0, QColor(255, 255, 255)) linear_grad.setColorAt(1, QColor(192, 192, 255)) self.setBackgroundBrush(linear_grad) diff --git a/examples/widgets/gettext/doc/gettext.rst b/examples/widgets/gettext/doc/gettext.rst new file mode 100644 index 00000000..ea1127fe --- /dev/null +++ b/examples/widgets/gettext/doc/gettext.rst @@ -0,0 +1,7 @@ +.. _gettext-example: + +GNU gettext Example +=================== + +This example demonstrates the use of GNU gettext for translating +applications as described in :ref:`translations`. diff --git a/examples/widgets/graphicsview/anchorlayout/anchorlayout.py b/examples/widgets/graphicsview/anchorlayout/anchorlayout.py index d6e8e57b..9d9be041 100644 --- a/examples/widgets/graphicsview/anchorlayout/anchorlayout.py +++ b/examples/widgets/graphicsview/anchorlayout/anchorlayout.py @@ -41,7 +41,7 @@ if __name__ == '__main__': f = create_item(QSizeF(30, 50), QSizeF(150, 50), max_size, "F") g = create_item(QSizeF(30, 50), QSizeF(30, 100), max_size, "G") - l = QGraphicsAnchorLayout() + l = QGraphicsAnchorLayout() # noqa: E741 l.setSpacing(0) w = QGraphicsWidget(None, Qt.Window) diff --git a/examples/widgets/graphicsview/collidingmice/collidingmice.py b/examples/widgets/graphicsview/collidingmice/collidingmice.py index 53c5c1aa..fb24db3a 100644 --- a/examples/widgets/graphicsview/collidingmice/collidingmice.py +++ b/examples/widgets/graphicsview/collidingmice/collidingmice.py @@ -5,14 +5,11 @@ import math import sys -from PySide6.QtCore import (QLineF, QPointF, QRandomGenerator, QRectF, QTimer, - Qt) -from PySide6.QtGui import (QBrush, QColor, QPainter, QPainterPath, QPixmap, - QPolygonF, QTransform) -from PySide6.QtWidgets import (QApplication, QGraphicsItem, QGraphicsScene, - QGraphicsView) +from PySide6.QtCore import (QLineF, QPointF, QRandomGenerator, QRectF, QTimer, Qt) +from PySide6.QtGui import (QBrush, QColor, QPainter, QPainterPath, QPixmap, QPolygonF, QTransform) +from PySide6.QtWidgets import (QApplication, QGraphicsItem, QGraphicsScene, QGraphicsView) -import mice_rc +import mice_rc # noqa: F401 def random(boundary): @@ -26,7 +23,7 @@ class Mouse(QGraphicsItem): # Create the bounding rectangle once. adjust = 0.5 BOUNDING_RECT = QRectF(-20 - adjust, -22 - adjust, 40 + adjust, - 83 + adjust) + 83 + adjust) def __init__(self): super().__init__() @@ -103,7 +100,8 @@ class Mouse(QGraphicsItem): if angle_to_center < Mouse.PI and angle_to_center > Mouse.PI / 4: # Rotate left. self.angle += [-0.25, 0.25][self.angle < -Mouse.PI / 2] - elif angle_to_center >= Mouse.PI and angle_to_center < (Mouse.PI + Mouse.PI / 2 + Mouse.PI / 4): + elif (angle_to_center >= Mouse.PI + and angle_to_center < (Mouse.PI + Mouse.PI / 2 + Mouse.PI / 4)): # Rotate right. self.angle += [-0.25, 0.25][self.angle < Mouse.PI / 2] elif math.sin(self.angle) < 0: diff --git a/examples/widgets/graphicsview/diagramscene/diagramscene.py b/examples/widgets/graphicsview/diagramscene/diagramscene.py index 3348577a..60e05613 100644 --- a/examples/widgets/graphicsview/diagramscene/diagramscene.py +++ b/examples/widgets/graphicsview/diagramscene/diagramscene.py @@ -17,7 +17,7 @@ from PySide6.QtWidgets import (QAbstractButton, QApplication, QButtonGroup, QMessageBox, QSizePolicy, QToolBox, QToolButton, QWidget) -import diagramscene_rc +import diagramscene_rc # noqa: F401 class Arrow(QGraphicsLineItem): @@ -30,8 +30,7 @@ class Arrow(QGraphicsLineItem): self._my_end_item = endItem self.setFlag(QGraphicsItem.ItemIsSelectable, True) self._my_color = Qt.black - self.setPen(QPen(self._my_color, 2, Qt.SolidLine, - Qt.RoundCap, Qt.RoundJoin)) + self.setPen(QPen(self._my_color, 2, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin)) def set_color(self, color): self._my_color = color @@ -163,19 +162,19 @@ class DiagramItem(QGraphicsPolygonItem): self._my_polygon = path.toFillPolygon() elif self.diagram_type == self.Conditional: self._my_polygon = QPolygonF([ - QPointF(-100, 0), QPointF(0, 100), - QPointF(100, 0), QPointF(0, -100), - QPointF(-100, 0)]) + QPointF(-100, 0), QPointF(0, 100), + QPointF(100, 0), QPointF(0, -100), + QPointF(-100, 0)]) elif self.diagram_type == self.Step: self._my_polygon = QPolygonF([ - QPointF(-100, -100), QPointF(100, -100), - QPointF(100, 100), QPointF(-100, 100), - QPointF(-100, -100)]) + QPointF(-100, -100), QPointF(100, -100), + QPointF(100, 100), QPointF(-100, 100), + QPointF(-100, -100)]) else: self._my_polygon = QPolygonF([ - QPointF(-120, -80), QPointF(-70, 80), - QPointF(120, 80), QPointF(70, -80), - QPointF(-120, -80)]) + QPointF(-120, -80), QPointF(-70, 80), + QPointF(120, 80), QPointF(70, -80), + QPointF(-120, -80)]) self.setPolygon(self._my_polygon) self.setFlag(QGraphicsItem.ItemIsMovable, True) @@ -291,8 +290,7 @@ class DiagramScene(QGraphicsScene): item.setPos(mouseEvent.scenePos()) self.item_inserted.emit(item) elif self._my_mode == self.InsertLine: - self.line = QGraphicsLineItem(QLineF(mouseEvent.scenePos(), - mouseEvent.scenePos())) + self.line = QGraphicsLineItem(QLineF(mouseEvent.scenePos(), mouseEvent.scenePos())) self.line.setPen(QPen(self._my_line_color, 2)) self.addItem(self.line) elif self._my_mode == self.InsertText: @@ -328,10 +326,10 @@ class DiagramScene(QGraphicsScene): self.removeItem(self.line) self.line = None - if (len(start_items) and len(end_items) and - isinstance(start_items[0], DiagramItem) and - isinstance(end_items[0], DiagramItem) and - start_items[0] != end_items[0]): + if (len(start_items) and len(end_items) + and isinstance(start_items[0], DiagramItem) + and isinstance(end_items[0], DiagramItem) + and start_items[0] != end_items[0]): start_item = start_items[0] end_item = end_items[0] arrow = Arrow(start_item, end_item) @@ -484,24 +482,21 @@ class MainWindow(QMainWindow): def text_color_changed(self): self._text_action = self.sender() self._font_color_tool_button.setIcon(self.create_color_tool_button_icon( - ':/images/textpointer.png', - QColor(self._text_action.data()))) + ':/images/textpointer.png', QColor(self._text_action.data()))) self.text_button_triggered() @Slot() def item_color_changed(self): self._fill_action = self.sender() self._fill_color_tool_button.setIcon(self.create_color_tool_button_icon( - ':/images/floodfill.png', - QColor(self._fill_action.data()))) + ':/images/floodfill.png', QColor(self._fill_action.data()))) self.fill_button_triggered() @Slot() def line_color_changed(self): self._line_action = self.sender() self._line_color_tool_button.setIcon(self.create_color_tool_button_icon( - ':/images/linecolor.png', - QColor(self._line_action.data()))) + ':/images/linecolor.png', QColor(self._line_action.data()))) self.line_button_triggered() @Slot() @@ -541,7 +536,7 @@ class MainWindow(QMainWindow): @Slot() def about(self): QMessageBox.about(self, "About Diagram Scene", - "The Diagram Scene example shows use of the graphics framework.") + "The Diagram Scene example shows use of the graphics framework.") def create_tool_box(self): self._button_group = QButtonGroup() @@ -549,12 +544,9 @@ class MainWindow(QMainWindow): self._button_group.idClicked.connect(self.button_group_clicked) layout = QGridLayout() - layout.addWidget(self.create_cell_widget("Conditional", DiagramItem.Conditional), - 0, 0) - layout.addWidget(self.create_cell_widget("Process", DiagramItem.Step), 0, - 1) - layout.addWidget(self.create_cell_widget("Input/Output", DiagramItem.Io), - 1, 0) + layout.addWidget(self.create_cell_widget("Conditional", DiagramItem.Conditional), 0, 0) + layout.addWidget(self.create_cell_widget("Process", DiagramItem.Step), 0, 1) + layout.addWidget(self.create_cell_widget("Input/Output", DiagramItem.Io), 1, 0) text_button = QToolButton() text_button.setCheckable(True) @@ -580,14 +572,14 @@ class MainWindow(QMainWindow): self._background_button_group.buttonClicked.connect(self.background_button_group_clicked) background_layout = QGridLayout() - background_layout.addWidget(self.create_background_cell_widget("Blue Grid", - ':/images/background1.png'), 0, 0) - background_layout.addWidget(self.create_background_cell_widget("White Grid", - ':/images/background2.png'), 0, 1) - background_layout.addWidget(self.create_background_cell_widget("Gray Grid", - ':/images/background3.png'), 1, 0) - background_layout.addWidget(self.create_background_cell_widget("No Grid", - ':/images/background4.png'), 1, 1) + background_layout.addWidget( + self.create_background_cell_widget("Blue Grid", ':/images/background1.png'), 0, 0) + background_layout.addWidget( + self.create_background_cell_widget("White Grid", ':/images/background2.png'), 0, 1) + background_layout.addWidget( + self.create_background_cell_widget("Gray Grid", ':/images/background3.png'), 1, 0) + background_layout.addWidget( + self.create_background_cell_widget("No Grid", ':/images/background4.png'), 1, 1) background_layout.setRowStretch(2, 10) background_layout.setColumnStretch(2, 10) @@ -603,38 +595,37 @@ class MainWindow(QMainWindow): def create_actions(self): self._to_front_action = QAction( - QIcon(':/images/bringtofront.png'), "Bring to &Front", - self, shortcut="Ctrl+F", statusTip="Bring item to front", - triggered=self.bring_to_front) + QIcon(':/images/bringtofront.png'), "Bring to &Front", + self, shortcut="Ctrl+F", statusTip="Bring item to front", + triggered=self.bring_to_front) self._send_back_action = QAction( - QIcon(':/images/sendtoback.png'), "Send to &Back", self, - shortcut="Ctrl+B", statusTip="Send item to back", - triggered=self.send_to_back) + QIcon(':/images/sendtoback.png'), "Send to &Back", self, + shortcut="Ctrl+B", statusTip="Send item to back", + triggered=self.send_to_back) self._delete_action = QAction(QIcon(':/images/delete.png'), - "&Delete", self, shortcut="Delete", - statusTip="Delete item from diagram", - triggered=self.delete_item) + "&Delete", self, shortcut="Delete", + statusTip="Delete item from diagram", + triggered=self.delete_item) self._exit_action = QAction("E&xit", self, shortcut="Ctrl+X", - statusTip="Quit Scenediagram example", triggered=self.close) + statusTip="Quit Scenediagram example", triggered=self.close) self._bold_action = QAction(QIcon(':/images/bold.png'), - "Bold", self, checkable=True, shortcut="Ctrl+B", - triggered=self.handle_font_change) + "Bold", self, checkable=True, shortcut="Ctrl+B", + triggered=self.handle_font_change) self._italic_action = QAction(QIcon(':/images/italic.png'), - "Italic", self, checkable=True, shortcut="Ctrl+I", - triggered=self.handle_font_change) + "Italic", self, checkable=True, shortcut="Ctrl+I", + triggered=self.handle_font_change) self._underline_action = QAction( - QIcon(':/images/underline.png'), "Underline", self, - checkable=True, shortcut="Ctrl+U", - triggered=self.handle_font_change) + QIcon(':/images/underline.png'), "Underline", self, + checkable=True, shortcut="Ctrl+U", + triggered=self.handle_font_change) - self._about_action = QAction("A&bout", self, shortcut="Ctrl+B", - triggered=self.about) + self._about_action = QAction("A&bout", self, shortcut="Ctrl+B", triggered=self.about) def create_menus(self): self._file_menu = self.menuBar().addMenu("&File") @@ -669,32 +660,29 @@ class MainWindow(QMainWindow): self._font_color_tool_button = QToolButton() self._font_color_tool_button.setPopupMode(QToolButton.MenuButtonPopup) self._font_color_tool_button.setMenu( - self.create_color_menu(self.text_color_changed, Qt.black)) + self.create_color_menu(self.text_color_changed, Qt.black)) self._text_action = self._font_color_tool_button.menu().defaultAction() self._font_color_tool_button.setIcon( - self.create_color_tool_button_icon(':/images/textpointer.png', - Qt.black)) + self.create_color_tool_button_icon(':/images/textpointer.png', Qt.black)) self._font_color_tool_button.setAutoFillBackground(True) self._font_color_tool_button.clicked.connect(self.text_button_triggered) self._fill_color_tool_button = QToolButton() self._fill_color_tool_button.setPopupMode(QToolButton.MenuButtonPopup) self._fill_color_tool_button.setMenu( - self.create_color_menu(self.item_color_changed, Qt.white)) + self.create_color_menu(self.item_color_changed, Qt.white)) self._fill_action = self._fill_color_tool_button.menu().defaultAction() self._fill_color_tool_button.setIcon( - self.create_color_tool_button_icon(':/images/floodfill.png', - Qt.white)) + self.create_color_tool_button_icon(':/images/floodfill.png', Qt.white)) self._fill_color_tool_button.clicked.connect(self.fill_button_triggered) self._line_color_tool_button = QToolButton() self._line_color_tool_button.setPopupMode(QToolButton.MenuButtonPopup) self._line_color_tool_button.setMenu( - self.create_color_menu(self.line_color_changed, Qt.black)) + self.create_color_menu(self.line_color_changed, Qt.black)) self._line_action = self._line_color_tool_button.menu().defaultAction() self._line_color_tool_button.setIcon( - self.create_color_tool_button_icon(':/images/linecolor.png', - Qt.black)) + self.create_color_tool_button_icon(':/images/linecolor.png', Qt.black)) self._line_color_tool_button.clicked.connect(self.line_button_triggered) self._text_tool_bar = self.addToolBar("Font") @@ -719,8 +707,7 @@ class MainWindow(QMainWindow): self._pointer_type_group = QButtonGroup() self._pointer_type_group.addButton(pointer_button, DiagramScene.MoveItem) - self._pointer_type_group.addButton(line_pointer_button, - DiagramScene.InsertLine) + self._pointer_type_group.addButton(line_pointer_button, DiagramScene.InsertLine) self._pointer_type_group.idClicked.connect(self.pointer_group_clicked) self._scene_scale_combo = QComboBox() @@ -775,8 +762,7 @@ class MainWindow(QMainWindow): color_menu = QMenu(self) for color, name in zip(colors, names): - action = QAction(self.create_color_icon(color), name, self, - triggered=slot) + action = QAction(self.create_color_icon(color), name, self, triggered=slot) action.setData(QColor(color)) color_menu.addAction(action) if color == defaultColor: diff --git a/examples/widgets/graphicsview/dragdroprobot/dragdroprobot.py b/examples/widgets/graphicsview/dragdroprobot/dragdroprobot.py index 0bfd3e0d..9ed92b26 100644 --- a/examples/widgets/graphicsview/dragdroprobot/dragdroprobot.py +++ b/examples/widgets/graphicsview/dragdroprobot/dragdroprobot.py @@ -13,7 +13,7 @@ from PySide6.QtWidgets import (QApplication, QGraphicsItem, QGraphicsItemAnimation, QGraphicsScene, QGraphicsView) -import dragdroprobot_rc +import dragdroprobot_rc # noqa: F401 def random(boundary): @@ -103,8 +103,8 @@ class RobotPart(QGraphicsItem): self.setAcceptDrops(True) def dragEnterEvent(self, event): - if (event.mimeData().hasColor() or - (isinstance(self, RobotHead) and event.mimeData().hasImage())): + if (event.mimeData().hasColor() + or (isinstance(self, RobotHead) and event.mimeData().hasImage())): event.setAccepted(True) self._drag_over = True self.update() @@ -131,8 +131,7 @@ class RobotHead(RobotPart): def paint(self, painter, option, widget=None): if not self.pixmap: - painter.setBrush(self._drag_over and self.color.lighter(130) - or self.color) + painter.setBrush(self._drag_over and self.color.lighter(130) or self.color) painter.drawRoundedRect(-10, -30, 20, 30, 25, 25, Qt.RelativeSize) painter.setBrush(Qt.white) painter.drawEllipse(-7, -3 - 20, 7, 7) @@ -154,7 +153,7 @@ class RobotTorso(RobotPart): def paint(self, painter, option, widget=None): painter.setBrush(self._drag_over and self.color.lighter(130) - or self.color) + or self.color) painter.drawRoundedRect(-20, -20, 40, 60, 25, 25, Qt.RelativeSize) painter.drawEllipse(-25, -20, 20, 20) painter.drawEllipse(5, -20, 20, 20) @@ -169,7 +168,7 @@ class RobotLimb(RobotPart): def paint(self, painter, option, widget=None): painter.setBrush(self._drag_over and self.color.lighter(130) or self.color) painter.drawRoundedRect(self.boundingRect(), 50, 50, - Qt.RelativeSize) + Qt.RelativeSize) painter.drawEllipse(-5, -5, 10, 10) @@ -177,35 +176,35 @@ class Robot(RobotPart): def __init__(self): super().__init__() - self.torsoItem = RobotTorso(self) - self.headItem = RobotHead(self.torsoItem) - self.upperLeftArmItem = RobotLimb(self.torsoItem) - self.lowerLeftArmItem = RobotLimb(self.upperLeftArmItem) + self.torsoItem = RobotTorso(self) + self.headItem = RobotHead(self.torsoItem) + self.upperLeftArmItem = RobotLimb(self.torsoItem) + self.lowerLeftArmItem = RobotLimb(self.upperLeftArmItem) self._upper_right_arm_item = RobotLimb(self.torsoItem) self._lower_right_arm_item = RobotLimb(self._upper_right_arm_item) self._upper_right_leg_item = RobotLimb(self.torsoItem) self._lower_right_leg_item = RobotLimb(self._upper_right_leg_item) - self.upperLeftLegItem = RobotLimb(self.torsoItem) - self.lowerLeftLegItem = RobotLimb(self.upperLeftLegItem) + self.upperLeftLegItem = RobotLimb(self.torsoItem) + self.lowerLeftLegItem = RobotLimb(self.upperLeftLegItem) self.timeline = QTimeLine() settings = [ - # item position rotation at - # x y time 0 / 1 - ( self.headItem, 0, -18, 20, -20 ), - ( self.upperLeftArmItem, -15, -10, 190, 180 ), - ( self.lowerLeftArmItem, 30, 0, 50, 10 ), - ( self._upper_right_arm_item, 15, -10, 300, 310 ), - ( self._lower_right_arm_item, 30, 0, 0, -70 ), - ( self._upper_right_leg_item, 10, 32, 40, 120 ), - ( self._lower_right_leg_item, 30, 0, 10, 50 ), - ( self.upperLeftLegItem, -10, 32, 150, 80 ), - ( self.lowerLeftLegItem, 30, 0, 70, 10 ), - ( self.torsoItem, 0, 0, 5, -20 ) + # item position rotation at + # x y time 0 / 1 + (self.headItem, 0, -18, 20, -20), # noqa: E241 + (self.upperLeftArmItem, -15, -10, 190, 180), # noqa: E241 + (self.lowerLeftArmItem, 30, 0, 50, 10), # noqa: E241 + (self._upper_right_arm_item, 15, -10, 300, 310), # noqa: E241 + (self._lower_right_arm_item, 30, 0, 0, -70), # noqa: E241 + (self._upper_right_leg_item, 10, 32, 40, 120), # noqa: E241 + (self._lower_right_leg_item, 30, 0, 10, 50), # noqa: E241 + (self.upperLeftLegItem, -10, 32, 150, 80), # noqa: E241 + (self.lowerLeftLegItem, 30, 0, 70, 10), # noqa: E241 + (self.torsoItem, 0, 0, 5, -20) # noqa: E241 ] self.animations = [] for item, pos_x, pos_y, rotation1, rotation2 in settings: - item.setPos(pos_x,pos_y) + item.setPos(pos_x, pos_y) animation = QGraphicsItemAnimation() animation.setItem(item) animation.setTimeLine(self.timeline) @@ -228,7 +227,7 @@ class Robot(RobotPart): pass -if __name__== '__main__': +if __name__ == '__main__': app = QApplication(sys.argv) scene = QGraphicsScene(-200, -200, 400, 400) diff --git a/examples/widgets/graphicsview/elasticnodes/elasticnodes.py b/examples/widgets/graphicsview/elasticnodes/elasticnodes.py index 58d9d4a9..90cb4962 100644 --- a/examples/widgets/graphicsview/elasticnodes/elasticnodes.py +++ b/examples/widgets/graphicsview/elasticnodes/elasticnodes.py @@ -20,8 +20,6 @@ def random(boundary): class Edge(QGraphicsItem): - item_type = QGraphicsItem.UserType + 2 - def __init__(self, sourceNode, destNode): super().__init__() @@ -36,7 +34,7 @@ class Edge(QGraphicsItem): self.adjust() def item_type(self): - return Edge.item_type + return QGraphicsItem.UserType + 2 def source_node(self): return self.source() @@ -119,7 +117,6 @@ class Edge(QGraphicsItem): class Node(QGraphicsItem): - item_type = QGraphicsItem.UserType + 1 def __init__(self, graphWidget): super().__init__() @@ -133,7 +130,7 @@ class Node(QGraphicsItem): self.setZValue(-1) def item_type(self): - return Node.item_type + return QGraphicsItem.UserType + 1 def add_edge(self, edge): self._edge_list.append(weakref.ref(edge)) @@ -157,7 +154,7 @@ class Node(QGraphicsItem): line = QLineF(self.mapFromItem(item, 0, 0), QPointF(0, 0)) dx = line.dx() dy = line.dy() - l = 2.0 * (dx * dx + dy * dy) + l = 2.0 * (dx * dx + dy * dy) # noqa: E741 if l > 0: xvel += (dx * 150.0) / l yvel += (dy * 150.0) / l @@ -192,7 +189,7 @@ class Node(QGraphicsItem): def boundingRect(self): adjust = 2.0 return QRectF(-10 - adjust, -10 - adjust, - 23 + adjust, 23 + adjust) + 23 + adjust, 23 + adjust) def shape(self): path = QPainterPath() @@ -348,9 +345,9 @@ class GraphWidget(QGraphicsView): bottom_shadow = QRectF(scene_rect.left() + 5, scene_rect.bottom(), scene_rect.width(), 5) if right_shadow.intersects(rect) or right_shadow.contains(rect): - painter.fillRect(right_shadow, Qt.darkGray) + painter.fillRect(right_shadow, Qt.darkGray) if bottom_shadow.intersects(rect) or bottom_shadow.contains(rect): - painter.fillRect(bottom_shadow, Qt.darkGray) + painter.fillRect(bottom_shadow, Qt.darkGray) # Fill. gradient = QLinearGradient(scene_rect.topLeft(), scene_rect.bottomRight()) @@ -362,7 +359,7 @@ class GraphWidget(QGraphicsView): # Text. text_rect = QRectF(scene_rect.left() + 4, scene_rect.top() + 4, - scene_rect.width() - 4, scene_rect.height() - 4) + scene_rect.width() - 4, scene_rect.height() - 4) message = self.tr("Click and drag the nodes around, and zoom with the " "mouse wheel or the '+' and '-' keys") @@ -376,7 +373,8 @@ class GraphWidget(QGraphicsView): painter.drawText(text_rect, message) def scale_view(self, scaleFactor): - factor = self.transform().scale(scaleFactor, scaleFactor).mapRect(QRectF(0, 0, 1, 1)).width() + factor = self.transform().scale(scaleFactor, scaleFactor).mapRect( + QRectF(0, 0, 1, 1)).width() if factor < 0.07 or factor > 100: return diff --git a/examples/widgets/imageviewer/imageviewer.py b/examples/widgets/imageviewer/imageviewer.py index ded7f246..93b1e87b 100644 --- a/examples/widgets/imageviewer/imageviewer.py +++ b/examples/widgets/imageviewer/imageviewer.py @@ -34,7 +34,7 @@ class ImageViewer(QMainWindow): self._image_label = QLabel() self._image_label.setBackgroundRole(QPalette.Base) self._image_label.setSizePolicy(QSizePolicy.Ignored, - QSizePolicy.Ignored) + QSizePolicy.Ignored) self._image_label.setScaledContents(True) self._scroll_area = QScrollArea() diff --git a/examples/widgets/itemviews/address_book/adddialogwidget.py b/examples/widgets/itemviews/address_book/adddialogwidget.py index 276a8d2b..ecb853e8 100644 --- a/examples/widgets/itemviews/address_book/adddialogwidget.py +++ b/examples/widgets/itemviews/address_book/adddialogwidget.py @@ -15,8 +15,8 @@ class AddDialogWidget(QDialog): name_label = QLabel("Name") address_label = QLabel("Address") - button_box = QDialogButtonBox(QDialogButtonBox.Ok | - QDialogButtonBox.Cancel) + button_box = QDialogButtonBox(QDialogButtonBox.Ok + | QDialogButtonBox.Cancel) self._name_text = QLineEdit() self._address_text = QTextEdit() diff --git a/examples/widgets/itemviews/address_book/address_book.py b/examples/widgets/itemviews/address_book/address_book.py index 2e1f6b9b..af0cf3de 100644 --- a/examples/widgets/itemviews/address_book/address_book.py +++ b/examples/widgets/itemviews/address_book/address_book.py @@ -25,16 +25,19 @@ class MainWindow(QMainWindow): tool_menu = self.menuBar().addMenu("&Tools") # Populate the File menu - open_action = self.create_action("&Open...", file_menu, self.open_file) - save_action = self.create_action("&Save As...", file_menu, self.save_file) + self.open_action = self.create_action("&Open...", file_menu, self.open_file) + self.save_action = self.create_action("&Save As...", file_menu, self.save_file) file_menu.addSeparator() - exit_action = self.create_action("E&xit", file_menu, self.close) + self.exit_action = self.create_action("E&xit", file_menu, self.close) # Populate the Tools menu - add_action = self.create_action("&Add Entry...", tool_menu, self._address_widget.add_entry) - self._edit_action = self.create_action("&Edit Entry...", tool_menu, self._address_widget.edit_entry) + self.add_action = self.create_action( + "&Add Entry...", tool_menu, self._address_widget.add_entry) + self._edit_action = self.create_action( + "&Edit Entry...", tool_menu, self._address_widget.edit_entry) tool_menu.addSeparator() - self._remove_action = self.create_action("&Remove Entry", tool_menu, self._address_widget.remove_entry) + self._remove_action = self.create_action( + "&Remove Entry", tool_menu, self._address_widget.remove_entry) # Disable the edit and remove menu items initially, as there are # no items yet. diff --git a/examples/widgets/itemviews/address_book/addresswidget.py b/examples/widgets/itemviews/address_book/addresswidget.py index ab1330e4..cb2f46ea 100644 --- a/examples/widgets/itemviews/address_book/addresswidget.py +++ b/examples/widgets/itemviews/address_book/addresswidget.py @@ -166,8 +166,9 @@ class AddressWidget(QTabWidget): proxy_model.setFilterKeyColumn(0) # Filter on the "name" column proxy_model.sort(0, Qt.AscendingOrder) - # This prevents an application crash (see: https://www.qtcentre.org/threads/58874-QListView-SelectionModel-selectionChanged-Crash) - viewselectionmodel = table_view.selectionModel() + # This prevents an application crash (see: + # https://www.qtcentre.org/threads/58874-QListView-SelectionModel-selectionChanged-Crash) # noqa: E501 + self.viewselectionmodel = table_view.selectionModel() table_view.selectionModel().selectionChanged.connect(self.selection_changed) self.addTab(table_view, group) diff --git a/examples/widgets/itemviews/address_book/tablemodel.py b/examples/widgets/itemviews/address_book/tablemodel.py index a0d63bbe..3c1dbd4c 100644 --- a/examples/widgets/itemviews/address_book/tablemodel.py +++ b/examples/widgets/itemviews/address_book/tablemodel.py @@ -105,5 +105,5 @@ class TableModel(QAbstractTableModel): """ if not index.isValid(): return Qt.ItemIsEnabled - return Qt.ItemFlags(QAbstractTableModel.flags(self, index) | - Qt.ItemIsEditable) + return Qt.ItemFlags(QAbstractTableModel.flags(self, index) + | Qt.ItemIsEditable) diff --git a/examples/widgets/itemviews/basicfiltermodel/basicsortfiltermodel.py b/examples/widgets/itemviews/basicfiltermodel/basicsortfiltermodel.py index 83423740..a30b0abd 100644 --- a/examples/widgets/itemviews/basicfiltermodel/basicsortfiltermodel.py +++ b/examples/widgets/itemviews/basicfiltermodel/basicsortfiltermodel.py @@ -46,11 +46,11 @@ class Window(QWidget): self._filter_syntax_combo_box = QComboBox() self._filter_syntax_combo_box.addItem("Regular expression", - REGULAR_EXPRESSION) + REGULAR_EXPRESSION) self._filter_syntax_combo_box.addItem("Wildcard", - WILDCARD) + WILDCARD) self._filter_syntax_combo_box.addItem("Fixed string", - FIXED_STRING) + FIXED_STRING) self._filter_syntax_label = QLabel("Filter &syntax:") self._filter_syntax_label.setBuddy(self._filter_syntax_combo_box) @@ -147,25 +147,25 @@ def create_mail_model(parent): model.setHeaderData(2, Qt.Horizontal, "Date") add_mail(model, "Happy New Year!", "Grace K. ", - QDateTime(QDate(2006, 12, 31), QTime(17, 3))) + QDateTime(QDate(2006, 12, 31), QTime(17, 3))) add_mail(model, "Radically new concept", "Grace K. ", - QDateTime(QDate(2006, 12, 22), QTime(9, 44))) + QDateTime(QDate(2006, 12, 22), QTime(9, 44))) add_mail(model, "Accounts", "pascale@nospam.com", - QDateTime(QDate(2006, 12, 31), QTime(12, 50))) + QDateTime(QDate(2006, 12, 31), QTime(12, 50))) add_mail(model, "Expenses", "Joe Bloggs ", - QDateTime(QDate(2006, 12, 25), QTime(11, 39))) + QDateTime(QDate(2006, 12, 25), QTime(11, 39))) add_mail(model, "Re: Expenses", "Andy ", - QDateTime(QDate(2007, 1, 2), QTime(16, 5))) + QDateTime(QDate(2007, 1, 2), QTime(16, 5))) add_mail(model, "Re: Accounts", "Joe Bloggs ", - QDateTime(QDate(2007, 1, 3), QTime(14, 18))) + QDateTime(QDate(2007, 1, 3), QTime(14, 18))) add_mail(model, "Re: Accounts", "Andy ", - QDateTime(QDate(2007, 1, 3), QTime(14, 26))) + QDateTime(QDate(2007, 1, 3), QTime(14, 26))) add_mail(model, "Sports", "Linda Smith ", - QDateTime(QDate(2007, 1, 5), QTime(11, 33))) + QDateTime(QDate(2007, 1, 5), QTime(11, 33))) add_mail(model, "AW: Sports", "Rolf Newschweinstein ", - QDateTime(QDate(2007, 1, 5), QTime(12, 0))) + QDateTime(QDate(2007, 1, 5), QTime(12, 0))) add_mail(model, "RE: Sports", "Petra Schmidt ", - QDateTime(QDate(2007, 1, 5), QTime(12, 1))) + QDateTime(QDate(2007, 1, 5), QTime(12, 1))) return model diff --git a/examples/widgets/itemviews/dirview/dirview.py b/examples/widgets/itemviews/dirview/dirview.py index aa1e6218..d1be6958 100644 --- a/examples/widgets/itemviews/dirview/dirview.py +++ b/examples/widgets/itemviews/dirview/dirview.py @@ -57,4 +57,3 @@ if __name__ == "__main__": tree.show() sys.exit(app.exec()) - diff --git a/examples/widgets/itemviews/editabletreemodel/mainwindow.py b/examples/widgets/itemviews/editabletreemodel/mainwindow.py index c6abfa56..1489bf28 100644 --- a/examples/widgets/itemviews/editabletreemodel/mainwindow.py +++ b/examples/widgets/itemviews/editabletreemodel/mainwindow.py @@ -52,7 +52,7 @@ class MainWindow(QMainWindow): self.insert_child_action.setShortcut("Ctrl+N") self.insert_child_action.triggered.connect(self.insert_child) help_menu = menubar.addMenu("&Help") - about_qt_action = help_menu.addAction("About Qt", qApp.aboutQt) + about_qt_action = help_menu.addAction("About Qt", qApp.aboutQt) # noqa: F821 about_qt_action.setShortcut("F1") self.setWindowTitle("Editable Tree Model") diff --git a/examples/widgets/itemviews/fetchmore/fetchmore.py b/examples/widgets/itemviews/fetchmore/fetchmore.py index ecee86e3..5150250e 100644 --- a/examples/widgets/itemviews/fetchmore/fetchmore.py +++ b/examples/widgets/itemviews/fetchmore/fetchmore.py @@ -49,7 +49,7 @@ class FileListModel(QAbstractListModel): if role == Qt.BackgroundRole: batch = row // BATCH_SIZE - palette = qApp.palette() + palette = qApp.palette() # noqa: F821 return palette.base() if batch % 2 == 0 else palette.alternateBase() if role == Qt.DecorationRole: @@ -112,7 +112,7 @@ class Window(QWidget): self.setWindowTitle("Fetch More Example") - @Slot(str,int,int,int) + @Slot(str, int, int, int) def update_log(self, path, start, number, total): native_path = QDir.toNativeSeparators(path) last = start + number - 1 diff --git a/examples/widgets/itemviews/spinboxdelegate/spinboxdelegate.py b/examples/widgets/itemviews/spinboxdelegate/spinboxdelegate.py index 266b8c1e..577f0faa 100644 --- a/examples/widgets/itemviews/spinboxdelegate/spinboxdelegate.py +++ b/examples/widgets/itemviews/spinboxdelegate/spinboxdelegate.py @@ -10,6 +10,7 @@ from PySide6.QtCore import QModelIndex """PySide6 port of the widgets/itemviews/spinboxdelegate from Qt v6.x""" + #! [0] class SpinBoxDelegate(QStyledItemDelegate): """A delegate that allows the user to change integer values from the model @@ -52,7 +53,7 @@ class SpinBoxDelegate(QStyledItemDelegate): if __name__ == '__main__': app = QApplication(sys.argv) - model= QStandardItemModel(4, 2) + model = QStandardItemModel(4, 2) tableView = QTableView() tableView.setModel(model) diff --git a/examples/widgets/itemviews/stardelegate/stardelegate.py b/examples/widgets/itemviews/stardelegate/stardelegate.py index 87bf1ff4..973eb14f 100644 --- a/examples/widgets/itemviews/stardelegate/stardelegate.py +++ b/examples/widgets/itemviews/stardelegate/stardelegate.py @@ -111,15 +111,15 @@ if __name__ == "__main__": # Create and populate the tableWidget table_widget = QTableWidget(4, 4) table_widget.setItemDelegate(StarDelegate()) - table_widget.setEditTriggers(QAbstractItemView.DoubleClicked | - QAbstractItemView.SelectedClicked) + table_widget.setEditTriggers(QAbstractItemView.DoubleClicked + | QAbstractItemView.SelectedClicked) table_widget.setSelectionBehavior(QAbstractItemView.SelectRows) table_widget.setHorizontalHeaderLabels(["Title", "Genre", "Artist", "Rating"]) - data = [ ["Mass in B-Minor", "Baroque", "J.S. Bach", 5], - ["Three More Foxes", "Jazz", "Maynard Ferguson", 4], - ["Sex Bomb", "Pop", "Tom Jones", 3], - ["Barbie Girl", "Pop", "Aqua", 5] ] + data = [["Mass in B-Minor", "Baroque", "J.S. Bach", 5], + ["Three More Foxes", "Jazz", "Maynard Ferguson", 4], + ["Sex Bomb", "Pop", "Tom Jones", 3], + ["Barbie Girl", "Pop", "Aqua", 5]] for r in range(len(data)): table_widget.setItem(r, 0, QTableWidgetItem(data[r][0])) diff --git a/examples/widgets/itemviews/stardelegate/stareditor.py b/examples/widgets/itemviews/stardelegate/stareditor.py index 1b44164a..296afa95 100644 --- a/examples/widgets/itemviews/stardelegate/stareditor.py +++ b/examples/widgets/itemviews/stardelegate/stareditor.py @@ -55,8 +55,7 @@ class StarEditor(QWidget): """ Calculate which star the user's mouse cursor is currently hovering over. """ - star = (x / (self.star_rating.sizeHint().width() / - self.star_rating.MAX_STAR_COUNT)) + 1 + star = (x / (self.star_rating.sizeHint().width() / self.star_rating.MAX_STAR_COUNT)) + 1 if (star <= 0) or (star > self.star_rating.MAX_STAR_COUNT): return -1 diff --git a/examples/widgets/itemviews/stardelegate/starrating.py b/examples/widgets/itemviews/stardelegate/starrating.py index 4d49f740..28dbacd6 100644 --- a/examples/widgets/itemviews/stardelegate/starrating.py +++ b/examples/widgets/itemviews/stardelegate/starrating.py @@ -23,13 +23,13 @@ class StarRating(object): self._star_polygon.append(QPointF(1.0, 0.5)) for i in range(1, 5): self._star_polygon.append(QPointF(0.5 + 0.5 * cos(0.8 * i * pi), - 0.5 + 0.5 * sin(0.8 * i * pi))) + 0.5 + 0.5 * sin(0.8 * i * pi))) # Create the diamond shape we'll show in the editor self._diamond_polygon = QPolygonF() diamond_points = [QPointF(0.4, 0.5), QPointF(0.5, 0.4), - QPointF(0.6, 0.5), QPointF(0.5, 0.6), - QPointF(0.4, 0.5)] + QPointF(0.6, 0.5), QPointF(0.5, 0.6), + QPointF(0.4, 0.5)] self._diamond_polygon.append(diamond_points) def sizeHint(self): diff --git a/examples/widgets/layouts/basiclayouts/basiclayouts.py b/examples/widgets/layouts/basiclayouts/basiclayouts.py index a2d29e71..827cb785 100644 --- a/examples/widgets/layouts/basiclayouts/basiclayouts.py +++ b/examples/widgets/layouts/basiclayouts/basiclayouts.py @@ -27,7 +27,7 @@ class Dialog(QDialog): big_editor = QTextEdit() big_editor.setPlainText("This widget takes up all the remaining space " - "in the top-level layout.") + "in the top-level layout.") button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) @@ -75,8 +75,7 @@ class Dialog(QDialog): layout.addWidget(line_edit, i + 1, 1) self._small_editor = QTextEdit() - self._small_editor.setPlainText("This widget takes up about two thirds " - "of the grid layout.") + self._small_editor.setPlainText("This widget takes up about two thirds of the grid layout.") layout.addWidget(self._small_editor, 0, 2, 4, 1) diff --git a/examples/widgets/layouts/dynamiclayouts/dynamiclayouts.py b/examples/widgets/layouts/dynamiclayouts/dynamiclayouts.py index 4e379151..c9dfcc73 100644 --- a/examples/widgets/layouts/dynamiclayouts/dynamiclayouts.py +++ b/examples/widgets/layouts/dynamiclayouts/dynamiclayouts.py @@ -74,8 +74,8 @@ class Dialog(QDialog): def show_help(self): QMessageBox.information(self, "Dynamic Layouts Help", - "This example shows how to change layouts " - "dynamically.") + "This example shows how to change layouts " + "dynamically.") def create_rotable_group_box(self): self._rotable_group_box = QGroupBox("Rotable Widgets") @@ -102,7 +102,8 @@ class Dialog(QDialog): buttons_orientation_combo_box = QComboBox() buttons_orientation_combo_box.addItem("Horizontal", Qt.Horizontal) buttons_orientation_combo_box.addItem("Vertical", Qt.Vertical) - buttons_orientation_combo_box.currentIndexChanged[int].connect(self.buttons_orientation_changed) + buttons_orientation_combo_box.currentIndexChanged[int].connect( + self.buttons_orientation_changed) self._buttons_orientation_combo_box = buttons_orientation_combo_box @@ -117,7 +118,8 @@ class Dialog(QDialog): close_button = self._button_box.addButton(QDialogButtonBox.Close) help_button = self._button_box.addButton(QDialogButtonBox.Help) - rotate_widgets_button = self._button_box.addButton("Rotate &Widgets", QDialogButtonBox.ActionRole) + rotate_widgets_button = self._button_box.addButton( + "Rotate &Widgets", QDialogButtonBox.ActionRole) rotate_widgets_button.clicked.connect(self.rotate_widgets) close_button.clicked.connect(self.close) diff --git a/examples/widgets/linguist/doc/linguist.rst b/examples/widgets/linguist/doc/linguist.rst new file mode 100644 index 00000000..24a49a61 --- /dev/null +++ b/examples/widgets/linguist/doc/linguist.rst @@ -0,0 +1,7 @@ +.. _qt-linguist-example: + +Qt Linguist Example +=================== + +This example demonstrates the use of Qt Linguist and related tools for translating +applications as described in :ref:`translations`. diff --git a/examples/widgets/linguist/main.py b/examples/widgets/linguist/main.py index 16655432..e4212e80 100644 --- a/examples/widgets/linguist/main.py +++ b/examples/widgets/linguist/main.py @@ -9,7 +9,7 @@ from PySide6.QtWidgets import (QAbstractItemView, QApplication, QListWidget, QMainWindow) -import linguist_rc +import linguist_rc # noqa: F401 class Window(QMainWindow): @@ -21,7 +21,7 @@ class Window(QMainWindow): quit_action.triggered.connect(self.close) help_menu = self.menuBar().addMenu(self.tr("&Help")) about_qt_action = help_menu.addAction(self.tr("About Qt")) - about_qt_action.triggered.connect(qApp.aboutQt) + about_qt_action.triggered.connect(qApp.aboutQt) # noqa: F821 self._list_widget = QListWidget() self._list_widget.setSelectionMode(QAbstractItemView.MultiSelection) diff --git a/examples/widgets/mainwindows/application/application.py b/examples/widgets/mainwindows/application/application.py index 320c421a..7b2fa067 100644 --- a/examples/widgets/mainwindows/application/application.py +++ b/examples/widgets/mainwindows/application/application.py @@ -11,7 +11,7 @@ from PySide6.QtGui import QAction, QIcon, QKeySequence from PySide6.QtWidgets import (QApplication, QFileDialog, QMainWindow, QMessageBox, QTextEdit) -import application_rc +import application_rc # noqa: F401 class MainWindow(QMainWindow): @@ -73,9 +73,9 @@ class MainWindow(QMainWindow): @Slot() def about(self): QMessageBox.about(self, "About Application", - "The Application example demonstrates how to write " - "modern GUI applications using Qt, with a menu bar, " - "toolbars, and a status bar.") + "The Application example demonstrates how to write " + "modern GUI applications using Qt, with a menu bar, " + "toolbars, and a status bar.") @Slot() def document_was_modified(self): @@ -84,50 +84,51 @@ class MainWindow(QMainWindow): def create_actions(self): icon = QIcon.fromTheme("document-new", QIcon(':/images/new.png')) self._new_act = QAction(icon, "&New", self, shortcut=QKeySequence.New, - statusTip="Create a new file", triggered=self.new_file) + statusTip="Create a new file", triggered=self.new_file) icon = QIcon.fromTheme("document-open", QIcon(':/images/open.png')) self._open_act = QAction(icon, "&Open...", self, - shortcut=QKeySequence.Open, statusTip="Open an existing file", - triggered=self.open) + shortcut=QKeySequence.Open, statusTip="Open an existing file", + triggered=self.open) icon = QIcon.fromTheme("document-save", QIcon(':/images/save.png')) self._save_act = QAction(icon, "&Save", self, - shortcut=QKeySequence.Save, - statusTip="Save the document to disk", triggered=self.save) + shortcut=QKeySequence.Save, + statusTip="Save the document to disk", triggered=self.save) self._save_as_act = QAction("Save &As...", self, - shortcut=QKeySequence.SaveAs, - statusTip="Save the document under a new name", - triggered=self.save_as) + shortcut=QKeySequence.SaveAs, + statusTip="Save the document under a new name", + triggered=self.save_as) self._exit_act = QAction("E&xit", self, shortcut="Ctrl+Q", - statusTip="Exit the application", triggered=self.close) + statusTip="Exit the application", triggered=self.close) icon = QIcon.fromTheme("edit-cut", QIcon(':/images/cut.png')) self._cut_act = QAction(icon, "Cu&t", self, shortcut=QKeySequence.Cut, - statusTip="Cut the current selection's contents to the clipboard", - triggered=self._text_edit.cut) + statusTip="Cut the current selection's contents to the clipboard", + triggered=self._text_edit.cut) icon = QIcon.fromTheme("edit-copy", QIcon(':/images/copy.png')) self._copy_act = QAction(icon, "&Copy", - self, shortcut=QKeySequence.Copy, - statusTip="Copy the current selection's contents to the clipboard", - triggered=self._text_edit.copy) + self, shortcut=QKeySequence.Copy, + statusTip="Copy the current selection's contents to the clipboard", + triggered=self._text_edit.copy) icon = QIcon.fromTheme("edit-paste", QIcon(':/images/paste.png')) self._paste_act = QAction(icon, "&Paste", - self, shortcut=QKeySequence.Paste, - statusTip="Paste the clipboard's contents into the current selection", - triggered=self._text_edit.paste) + self, shortcut=QKeySequence.Paste, + statusTip="Paste the clipboard's contents into the current " + "selection", + triggered=self._text_edit.paste) self._about_act = QAction("&About", self, - statusTip="Show the application's About box", - triggered=self.about) + statusTip="Show the application's About box", + triggered=self.about) self._about_qt_act = QAction("About &Qt", self, - statusTip="Show the Qt library's About box", - triggered=qApp.aboutQt) + statusTip="Show the Qt library's About box", + triggered=qApp.aboutQt) # noqa: F821 self._cut_act.setEnabled(False) self._copy_act.setEnabled(False) @@ -181,10 +182,9 @@ class MainWindow(QMainWindow): def maybe_save(self): if self._text_edit.document().isModified(): ret = QMessageBox.warning(self, "Application", - "The document has been modified.\nDo you want to save " - "your changes?", - QMessageBox.Save | QMessageBox.Discard | - QMessageBox.Cancel) + "The document has been modified.\nDo you want to save " + "your changes?", + QMessageBox.Save | QMessageBox.Discard | QMessageBox.Cancel) if ret == QMessageBox.Save: return self.save() elif ret == QMessageBox.Cancel: @@ -195,8 +195,7 @@ class MainWindow(QMainWindow): file = QFile(fileName) if not file.open(QFile.ReadOnly | QFile.Text): reason = file.errorString() - QMessageBox.warning(self, "Application", - f"Cannot read file {fileName}:\n{reason}.") + QMessageBox.warning(self, "Application", f"Cannot read file {fileName}:\n{reason}.") return inf = QTextStream(file) diff --git a/examples/widgets/mainwindows/dockwidgets/dockwidgets.py b/examples/widgets/mainwindows/dockwidgets/dockwidgets.py index 6d0e95a8..83487ee5 100644 --- a/examples/widgets/mainwindows/dockwidgets/dockwidgets.py +++ b/examples/widgets/mainwindows/dockwidgets/dockwidgets.py @@ -2,18 +2,20 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause -"""PySide6 port of the widgets/mainwindows/dockwidgets example from Qt v5.x, originating from PyQt""" +"""PySide6 port of the widgets/mainwindows/dockwidgets example from Qt v5.x, + originating from PyQt""" import sys from PySide6.QtCore import QDate, QFile, Qt, QTextStream from PySide6.QtGui import (QAction, QFont, QIcon, QKeySequence, - QTextCharFormat, QTextCursor, QTextTableFormat) + QTextCharFormat, QTextCursor, QTextTableFormat) from PySide6.QtPrintSupport import QPrintDialog, QPrinter from PySide6.QtWidgets import (QApplication, QDialog, QDockWidget, - QFileDialog, QListWidget, QMainWindow, QMessageBox, QTextEdit) + QFileDialog, QListWidget, QMainWindow, + QMessageBox, QTextEdit) -import dockwidgets_rc +import dockwidgets_rc # noqa: F401 class MainWindow(QMainWindow): @@ -62,8 +64,7 @@ class MainWindow(QMainWindow): cursor.insertBlock() cursor.insertText("Some Country") cursor.setPosition(top_frame.lastPosition()) - cursor.insertText(QDate.currentDate().toString("d MMMM yyyy"), - text_format) + cursor.insertText(QDate.currentDate().toString("d MMMM yyyy"), text_format) cursor.insertBlock() cursor.insertBlock() cursor.insertText("Dear ", text_format) @@ -103,7 +104,7 @@ class MainWindow(QMainWindow): if not file.open(QFile.WriteOnly | QFile.Text): reason = file.errorString() QMessageBox.warning(self, "Dock Widgets", - f"Cannot write file {filename}:\n{reason}.") + f"Cannot write file {filename}:\n{reason}.") return out = QTextStream(file) @@ -143,8 +144,8 @@ class MainWindow(QMainWindow): if cursor.isNull(): return cursor.beginEditBlock() - cursor.movePosition(QTextCursor.PreviousBlock, QTextCursor.MoveAnchor, - 2) + cursor.movePosition(QTextCursor.PreviousBlock, + QTextCursor.MoveAnchor, 2) cursor.insertBlock() cursor.insertText(paragraph) cursor.insertBlock() @@ -152,43 +153,44 @@ class MainWindow(QMainWindow): def about(self): QMessageBox.about(self, "About Dock Widgets", - "The Dock Widgets example demonstrates how to use " - "Qt's dock widgets. You can enter your own text, click a " - "customer to add a customer name and address, and click " - "standard paragraphs to add them.") + "The Dock Widgets example demonstrates how to use " + "Qt's dock widgets. You can enter your own text, click a " + "customer to add a customer name and address, and click " + "standard paragraphs to add them.") def create_actions(self): icon = QIcon.fromTheme('document-new', QIcon(':/images/new.png')) self._new_letter_act = QAction(icon, "&New Letter", - self, shortcut=QKeySequence.New, - statusTip="Create a new form letter", triggered=self.new_letter) + self, shortcut=QKeySequence.New, + statusTip="Create a new form letter", + triggered=self.new_letter) icon = QIcon.fromTheme('document-save', QIcon(':/images/save.png')) self._save_act = QAction(icon, "&Save...", self, - shortcut=QKeySequence.Save, - statusTip="Save the current form letter", triggered=self.save) + shortcut=QKeySequence.Save, + statusTip="Save the current form letter", triggered=self.save) icon = QIcon.fromTheme('document-print', QIcon(':/images/print.png')) self._print_act = QAction(icon, "&Print...", self, - shortcut=QKeySequence.Print, - statusTip="Print the current form letter", - triggered=self.print_) + shortcut=QKeySequence.Print, + statusTip="Print the current form letter", + triggered=self.print_) icon = QIcon.fromTheme('edit-undo', QIcon(':/images/undo.png')) self._undo_act = QAction(icon, "&Undo", self, - shortcut=QKeySequence.Undo, - statusTip="Undo the last editing action", triggered=self.undo) + shortcut=QKeySequence.Undo, + statusTip="Undo the last editing action", triggered=self.undo) self._quit_act = QAction("&Quit", self, shortcut="Ctrl+Q", - statusTip="Quit the application", triggered=self.close) + statusTip="Quit the application", triggered=self.close) self._about_act = QAction("&About", self, - statusTip="Show the application's About box", - triggered=self.about) + statusTip="Show the application's About box", + triggered=self.about) self._about_qt_act = QAction("About &Qt", self, - statusTip="Show the Qt library's About box", - triggered=QApplication.instance().aboutQt) + statusTip="Show the Qt library's About box", + triggered=QApplication.instance().aboutQt) def create_menus(self): self._file_menu = self.menuBar().addMenu("&File") @@ -241,21 +243,21 @@ class MainWindow(QMainWindow): self._paragraphs_list.addItems(( "Thank you for your payment which we have received today.", "Your order has been dispatched and should be with you within " - "28 days.", + "28 days.", "We have dispatched those items that were in stock. The rest of " - "your order will be dispatched once all the remaining items " - "have arrived at our warehouse. No additional shipping " - "charges will be made.", + "your order will be dispatched once all the remaining items " + "have arrived at our warehouse. No additional shipping " + "charges will be made.", "You made a small overpayment (less than $5) which we will keep " - "on account for you, or return at your request.", + "on account for you, or return at your request.", "You made a small underpayment (less than $1), but we have sent " - "your order anyway. We'll add this underpayment to your next " - "bill.", + "your order anyway. We'll add this underpayment to your next " + "bill.", "Unfortunately you did not send enough money. Please remit an " - "additional $. Your order will be dispatched as soon as the " - "complete amount has been received.", + "additional $. Your order will be dispatched as soon as the " + "complete amount has been received.", "You made an overpayment (more than $5). Do you wish to buy more " - "items, or should we return the excess to you?")) + "items, or should we return the excess to you?")) dock.setWidget(self._paragraphs_list) self.addDockWidget(Qt.RightDockWidgetArea, dock) self._view_menu.addAction(dock.toggleViewAction()) diff --git a/examples/widgets/mainwindows/mdi/mdi.py b/examples/widgets/mainwindows/mdi/mdi.py index 927a1daa..92c0c9c2 100644 --- a/examples/widgets/mainwindows/mdi/mdi.py +++ b/examples/widgets/mainwindows/mdi/mdi.py @@ -9,12 +9,12 @@ from functools import partial import sys from PySide6.QtCore import (QByteArray, QFile, QFileInfo, QSettings, - QSaveFile, QTextStream, Qt, Slot) + QSaveFile, QTextStream, Qt, Slot) from PySide6.QtGui import QAction, QIcon, QKeySequence from PySide6.QtWidgets import (QApplication, QFileDialog, QMainWindow, - QMdiArea, QMessageBox, QTextEdit) + QMdiArea, QMessageBox, QTextEdit) -import PySide6.QtExampleIcons +import PySide6.QtExampleIcons # noqa: F401 class MdiChild(QTextEdit): @@ -106,7 +106,7 @@ class MdiChild(QTextEdit): f = self.user_friendly_current_file() message = f"'{f}' has been modified.\nDo you want to save your changes?" ret = QMessageBox.warning(self, "MDI", message, - QMessageBox.Save | QMessageBox.Discard | QMessageBox.Cancel) + QMessageBox.Save | QMessageBox.Discard | QMessageBox.Cancel) if ret == QMessageBox.Save: return self.save() @@ -208,8 +208,8 @@ class MainWindow(QMainWindow): @Slot() def about(self): QMessageBox.about(self, "About MDI", - "The MDI example demonstrates how to write multiple " - "document interface applications using Qt.") + "The MDI example demonstrates how to write multiple " + "document interface applications using Qt.") @Slot() def update_menus(self): @@ -225,8 +225,8 @@ class MainWindow(QMainWindow): self._previous_act.setEnabled(has_mdi_child) self._separator_act.setVisible(has_mdi_child) - has_selection = (self.active_mdi_child() is not None and - self.active_mdi_child().textCursor().hasSelection()) + has_selection = (self.active_mdi_child() is not None + and self.active_mdi_child().textCursor().hasSelection()) self._cut_act.setEnabled(has_selection) self._copy_act.setEnabled(has_selection) @@ -273,80 +273,81 @@ class MainWindow(QMainWindow): icon = QIcon.fromTheme("document-new") self._new_act = QAction(icon, "&New", self, - shortcut=QKeySequence.New, statusTip="Create a new file", - triggered=self.new_file) + shortcut=QKeySequence.New, statusTip="Create a new file", + triggered=self.new_file) icon = QIcon.fromTheme("document-open") self._open_act = QAction(icon, "&Open...", self, - shortcut=QKeySequence.Open, statusTip="Open an existing file", - triggered=self.open) + shortcut=QKeySequence.Open, statusTip="Open an existing file", + triggered=self.open) icon = QIcon.fromTheme("document-save") self._save_act = QAction(icon, "&Save", self, - shortcut=QKeySequence.Save, - statusTip="Save the document to disk", triggered=self.save) + shortcut=QKeySequence.Save, + statusTip="Save the document to disk", triggered=self.save) self._save_as_act = QAction("Save &As...", self, - shortcut=QKeySequence.SaveAs, - statusTip="Save the document under a new name", - triggered=self.save_as) + shortcut=QKeySequence.SaveAs, + statusTip="Save the document under a new name", + triggered=self.save_as) self._exit_act = QAction("E&xit", self, shortcut=QKeySequence.Quit, - statusTip="Exit the application", - triggered=QApplication.instance().closeAllWindows) + statusTip="Exit the application", + triggered=QApplication.instance().closeAllWindows) icon = QIcon.fromTheme("edit-cut") self._cut_act = QAction(icon, "Cu&t", self, - shortcut=QKeySequence.Cut, - statusTip="Cut the current selection's contents to the clipboard", - triggered=self.cut) + shortcut=QKeySequence.Cut, + statusTip="Cut the current selection's contents to the clipboard", + triggered=self.cut) icon = QIcon.fromTheme("edit-copy") self._copy_act = QAction(icon, "&Copy", self, - shortcut=QKeySequence.Copy, - statusTip="Copy the current selection's contents to the clipboard", - triggered=self.copy) + shortcut=QKeySequence.Copy, + statusTip="Copy the current selection's contents to the clipboard", + triggered=self.copy) icon = QIcon.fromTheme("edit-paste") self._paste_act = QAction(icon, "&Paste", self, - shortcut=QKeySequence.Paste, - statusTip="Paste the clipboard's contents into the current selection", - triggered=self.paste) + shortcut=QKeySequence.Paste, + statusTip="Paste the clipboard's contents into the current " + "selection", + triggered=self.paste) self._close_act = QAction("Cl&ose", self, - statusTip="Close the active window", - triggered=self._mdi_area.closeActiveSubWindow) + statusTip="Close the active window", + triggered=self._mdi_area.closeActiveSubWindow) self._close_all_act = QAction("Close &All", self, - statusTip="Close all the windows", - triggered=self._mdi_area.closeAllSubWindows) + statusTip="Close all the windows", + triggered=self._mdi_area.closeAllSubWindows) self._tile_act = QAction("&Tile", self, statusTip="Tile the windows", - triggered=self._mdi_area.tileSubWindows) + triggered=self._mdi_area.tileSubWindows) self._cascade_act = QAction("&Cascade", self, - statusTip="Cascade the windows", - triggered=self._mdi_area.cascadeSubWindows) + statusTip="Cascade the windows", + triggered=self._mdi_area.cascadeSubWindows) self._next_act = QAction("Ne&xt", self, shortcut=QKeySequence.NextChild, - statusTip="Move the focus to the next window", - triggered=self._mdi_area.activateNextSubWindow) + statusTip="Move the focus to the next window", + triggered=self._mdi_area.activateNextSubWindow) self._previous_act = QAction("Pre&vious", self, - shortcut=QKeySequence.PreviousChild, - statusTip="Move the focus to the previous window", - triggered=self._mdi_area.activatePreviousSubWindow) + shortcut=QKeySequence.PreviousChild, + statusTip="Move the focus to the previous window", + triggered=self._mdi_area.activatePreviousSubWindow) self._separator_act = QAction(self) self._separator_act.setSeparator(True) self._about_act = QAction("&About", self, - statusTip="Show the application's About box", - triggered=self.about) + statusTip="Show the application's About box", + triggered=self.about) self._about_qt_act = QAction("About &Qt", self, - statusTip="Show the Qt library's About box", - triggered=QApplication.instance().aboutQt) + statusTip="Show the Qt library's About box", + triggered=QApplication.instance().aboutQt) def create_menus(self): self._file_menu = self.menuBar().addMenu("&File") @@ -435,7 +436,7 @@ if __name__ == '__main__': icon_paths = QIcon.themeSearchPaths() QIcon.setThemeSearchPaths(icon_paths + [":/qt-project.org/icons"]) - QIcon.setFallbackThemeName("example_icons"); + QIcon.setFallbackThemeName("example_icons") main_win = MainWindow() for f in options.files: diff --git a/examples/widgets/painting/basicdrawing/basicdrawing.py b/examples/widgets/painting/basicdrawing/basicdrawing.py index c1f2a5b0..858a8cd9 100644 --- a/examples/widgets/painting/basicdrawing/basicdrawing.py +++ b/examples/widgets/painting/basicdrawing/basicdrawing.py @@ -6,11 +6,12 @@ from PySide6.QtCore import QPoint, QRect, QSize, Qt, qVersion from PySide6.QtGui import (QBrush, QConicalGradient, QLinearGradient, QPainter, - QPainterPath, QPalette, QPen, QPixmap, QPolygon, QRadialGradient) + QPainterPath, QPalette, QPen, QPixmap, QPolygon, + QRadialGradient) from PySide6.QtWidgets import (QApplication, QCheckBox, QComboBox, QGridLayout, - QLabel, QSpinBox, QWidget) + QLabel, QSpinBox, QWidget) -import basicdrawing_rc +import basicdrawing_rc # noqa: F401 class RenderArea(QWidget): @@ -190,12 +191,9 @@ class Window(QWidget): pen_join_label.setBuddy(self._pen_join_combo_box) self._brush_style_combo_box = QComboBox() - self._brush_style_combo_box.addItem("Linear Gradient", - Qt.LinearGradientPattern) - self._brush_style_combo_box.addItem("Radial Gradient", - Qt.RadialGradientPattern) - self._brush_style_combo_box.addItem("Conical Gradient", - Qt.ConicalGradientPattern) + self._brush_style_combo_box.addItem("Linear Gradient", Qt.LinearGradientPattern) + self._brush_style_combo_box.addItem("Radial Gradient", Qt.RadialGradientPattern) + self._brush_style_combo_box.addItem("Conical Gradient", Qt.ConicalGradientPattern) self._brush_style_combo_box.addItem("Texture", Qt.TexturePattern) self._brush_style_combo_box.addItem("Solid", Qt.SolidPattern) self._brush_style_combo_box.addItem("Horizontal", Qt.HorPattern) @@ -260,24 +258,23 @@ class Window(QWidget): self.setWindowTitle("Basic Drawing") def shape_changed(self): - shape = self._shape_combo_box.itemData(self._shape_combo_box.currentIndex(), - id_role) + shape = self._shape_combo_box.itemData(self._shape_combo_box.currentIndex(), id_role) self._render_area.set_shape(shape) def pen_changed(self): width = self._pen_width_spin_box.value() style = Qt.PenStyle(self._pen_style_combo_box.itemData( - self._pen_style_combo_box.currentIndex(), id_role)) + self._pen_style_combo_box.currentIndex(), id_role)) cap = Qt.PenCapStyle(self._pen_cap_combo_box.itemData( - self._pen_cap_combo_box.currentIndex(), id_role)) + self._pen_cap_combo_box.currentIndex(), id_role)) join = Qt.PenJoinStyle(self._pen_join_combo_box.itemData( - self._pen_join_combo_box.currentIndex(), id_role)) + self._pen_join_combo_box.currentIndex(), id_role)) self._render_area.set_pen(QPen(Qt.blue, width, style, cap, join)) def brush_changed(self): style = Qt.BrushStyle(self._brush_style_combo_box.itemData( - self._brush_style_combo_box.currentIndex(), id_role)) + self._brush_style_combo_box.currentIndex(), id_role)) if style == Qt.LinearGradientPattern: linear_gradient = QLinearGradient(0, 0, 100, 100) diff --git a/examples/widgets/painting/concentriccircles/concentriccircles.py b/examples/widgets/painting/concentriccircles/concentriccircles.py index 0edc9cb9..d2c60178 100644 --- a/examples/widgets/painting/concentriccircles/concentriccircles.py +++ b/examples/widgets/painting/concentriccircles/concentriccircles.py @@ -2,12 +2,13 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause -"""PySide6 port of the widgets/painting/concentriccircles example from Qt v5.x, originating from PyQt""" +"""PySide6 port of the widgets/painting/concentriccircles example from Qt v5.x, originating + from PyQt""" from PySide6.QtCore import QRect, QRectF, QSize, Qt, QTimer from PySide6.QtGui import QColor, QPainter, QPalette, QPen from PySide6.QtWidgets import (QApplication, QFrame, QGridLayout, QLabel, - QSizePolicy, QWidget) + QSizePolicy, QWidget) class CircleWidget(QWidget): @@ -52,10 +53,10 @@ class CircleWidget(QWidget): if self._float_based: painter.drawEllipse(QRectF(-diameter / 2.0, - -diameter / 2.0, diameter, diameter)) + -diameter / 2.0, diameter, diameter)) else: painter.drawEllipse(QRect(-diameter / 2, - -diameter / 2, diameter, diameter)) + -diameter / 2, diameter, diameter)) class Window(QWidget): diff --git a/examples/widgets/painting/painter/painter.py b/examples/widgets/painting/painter/painter.py index 58584baf..2ca078ad 100644 --- a/examples/widgets/painting/painter/painter.py +++ b/examples/widgets/painting/painter/painter.py @@ -116,15 +116,17 @@ class MainWindow(QMainWindow): self.bar = self.addToolBar("Menu") self.bar.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) self._save_action = self.bar.addAction( - qApp.style().standardIcon(QStyle.SP_DialogSaveButton), "Save", self.on_save + qApp.style().standardIcon(QStyle.SP_DialogSaveButton), # noqa: F821 + "Save", self.on_save ) self._save_action.setShortcut(QKeySequence.Save) self._open_action = self.bar.addAction( - qApp.style().standardIcon(QStyle.SP_DialogOpenButton), "Open", self.on_open + qApp.style().standardIcon(QStyle.SP_DialogOpenButton), # noqa: F821 + "Open", self.on_open ) self._open_action.setShortcut(QKeySequence.Open) self.bar.addAction( - qApp.style().standardIcon(QStyle.SP_DialogResetButton), + qApp.style().standardIcon(QStyle.SP_DialogResetButton), # noqa: F821 "Clear", self.painter_widget.clear, ) diff --git a/examples/widgets/richtext/orderform/orderform.py b/examples/widgets/richtext/orderform/orderform.py index fd0be01f..9725624c 100644 --- a/examples/widgets/richtext/orderform/orderform.py +++ b/examples/widgets/richtext/orderform/orderform.py @@ -89,8 +89,7 @@ class MainWindow(QMainWindow): body_frame_format.setWidth(QTextLength(QTextLength.PercentageLength, 100)) cursor.insertFrame(body_frame_format) - cursor.insertText("I would like to place an order for the following " - "items:", text_format) + cursor.insertText("I would like to place an order for the following items:", text_format) cursor.insertBlock() cursor.insertBlock() @@ -121,17 +120,17 @@ class MainWindow(QMainWindow): cursor.insertBlock() cursor.insertText("Please update my records to take account of the " - "following privacy information:") + "following privacy information:") cursor.insertBlock() offers_table = cursor.insertTable(2, 2) cursor = offers_table.cellAt(0, 1).firstCursorPosition() cursor.insertText("I want to receive more information about your " - "company's products and special offers.", text_format) + "company's products and special offers.", text_format) cursor = offers_table.cellAt(1, 1).firstCursorPosition() cursor.insertText("I do not want to receive any promotional " - "information from your company.", text_format) + "information from your company.", text_format) if sendOffers: cursor = offers_table.cellAt(0, 0).firstCursorPosition() @@ -153,8 +152,8 @@ class MainWindow(QMainWindow): def create_sample(self): dialog = DetailsDialog('Dialog with default values', self) self.create_letter('Mr Smith', - '12 High Street\nSmall Town\nThis country', - dialog.order_items(), True) + '12 High Street\nSmall Town\nThis country', + dialog.order_items(), True) @Slot() def open_dialog(self): @@ -162,7 +161,7 @@ class MainWindow(QMainWindow): if dialog.exec() == QDialog.Accepted: self.create_letter(dialog.sender_name(), dialog.sender_address(), - dialog.order_items(), dialog.send_offers()) + dialog.order_items(), dialog.send_offers()) @Slot() def print_file(self): @@ -193,8 +192,7 @@ class DetailsDialog(QDialog): self._name_edit = QLineEdit() self._address_edit = QTextEdit() - self._offers_check_box = QCheckBox("Send information about " - "products and special offers:") + self._offers_check_box = QCheckBox("Send information about products and special offers:") self.setup_items_table() @@ -250,9 +248,9 @@ class DetailsDialog(QDialog): return answer = QMessageBox.warning(self, "Incomplete Form", - "The form does not contain all the necessary information.\n" - "Do you want to discard it?", - QMessageBox.Yes, QMessageBox.No) + "The form does not contain all the necessary information.\n" + "Do you want to discard it?", + QMessageBox.Yes, QMessageBox.No) if answer == QMessageBox.Yes: self.reject() diff --git a/examples/widgets/richtext/syntaxhighlighter/syntaxhighlighter.py b/examples/widgets/richtext/syntaxhighlighter/syntaxhighlighter.py index c329eefd..02132897 100644 --- a/examples/widgets/richtext/syntaxhighlighter/syntaxhighlighter.py +++ b/examples/widgets/richtext/syntaxhighlighter/syntaxhighlighter.py @@ -12,7 +12,7 @@ from PySide6.QtCore import (QFile, Qt, QTextStream) from PySide6.QtGui import (QColor, QFont, QFontDatabase, QKeySequence, QSyntaxHighlighter, QTextCharFormat) from PySide6.QtWidgets import (QApplication, QFileDialog, QMainWindow, - QPlainTextEdit) + QPlainTextEdit) class MainWindow(QMainWindow): @@ -81,7 +81,7 @@ class MainWindow(QMainWindow): quit_act.triggered.connect(self.close) help_menu = self.menuBar().addMenu("&Help") - help_menu.addAction("About &Qt", qApp.aboutQt) + help_menu.addAction("About &Qt", qApp.aboutQt) # noqa: F821 class Highlighter(QSyntaxHighlighter): diff --git a/examples/widgets/richtext/textedit/main.py b/examples/widgets/richtext/textedit/main.py index 88b679ed..330ea5fc 100644 --- a/examples/widgets/richtext/textedit/main.py +++ b/examples/widgets/richtext/textedit/main.py @@ -11,7 +11,7 @@ from PySide6.QtWidgets import QApplication from textedit import TextEdit -import textedit_rc +import textedit_rc # noqa: F401 if __name__ == '__main__': diff --git a/examples/widgets/richtext/textedit/textedit.py b/examples/widgets/richtext/textedit/textedit.py index 4f4146d3..a3940ae0 100644 --- a/examples/widgets/richtext/textedit/textedit.py +++ b/examples/widgets/richtext/textedit/textedit.py @@ -55,7 +55,7 @@ class TextEdit(QMainWindow): help_menu = self.menuBar().addMenu("Help") help_menu.addAction("About", self.about) - help_menu.addAction("About &Qt", qApp.aboutQt) + help_menu.addAction("About &Qt", qApp.aboutQt) # noqa: F821 text_font = QFont("Helvetica") text_font.setStyleHint(QFont.SansSerif) @@ -621,7 +621,7 @@ class TextEdit(QMainWindow): above = QTextCursor(cursor) above.movePosition(QTextCursor.Up) if (above.currentList() - and list_fmt.indent() + amount == above.currentList().format().indent()): + and list_fmt.indent() + amount == above.currentList().format().indent()): above.currentList().add(cursor.block()) else: list_fmt.setIndent(list_fmt.indent() + amount) diff --git a/examples/widgets/tools/regularexpression/regularexpressiondialog.py b/examples/widgets/tools/regularexpression/regularexpressiondialog.py index 4fc9c62a..2d2bb2bb 100644 --- a/examples/widgets/tools/regularexpression/regularexpressiondialog.py +++ b/examples/widgets/tools/regularexpression/regularexpressiondialog.py @@ -5,12 +5,14 @@ import re import logging from PySide6.QtCore import (QMargins, QRegularExpression, QRegularExpressionMatch, - QRegularExpressionMatchIterator, Qt, Slot,) + QRegularExpressionMatchIterator, Qt, Slot,) from PySide6.QtGui import (QAction, QColor, QContextMenuEvent, QFontDatabase, - QGuiApplication, QIcon, QPalette,) + QGuiApplication, QIcon, QPalette,) from PySide6.QtWidgets import (QCheckBox, QComboBox, QDialog, QFormLayout, - QFrame, QGridLayout, QHBoxLayout, QLabel, QLineEdit, QPlainTextEdit, - QSpinBox, QTreeWidget, QTreeWidgetItem, QVBoxLayout, QWidget,) + QFrame, QGridLayout, QHBoxLayout, QLabel, + QLineEdit, QPlainTextEdit, QSpinBox, + QTreeWidget, QTreeWidgetItem, QVBoxLayout, + QWidget) def rawStringLiteral(pattern: str) -> str: @@ -42,7 +44,7 @@ def codeToPattern(code: str) -> str: if code_characters[index] == '\\': del code_characters[index] code_characters_size -= 1 - index +=1 + index += 1 code = "".join(code_characters) if code.startswith('"') and code.endswith('"'): @@ -85,7 +87,7 @@ class PatternLineEdit(QLineEdit): t = ( t[: selection_start] + escapedSelection - + t[selection_start + len(selection) :] + + t[selection_start + len(selection):] ) self.setText(t) @@ -327,23 +329,20 @@ class RegularExpressionDialog(QDialog): self.patternOptionsCheckBoxLayout = QGridLayout() gridRow = 0 - self.patternOptionsCheckBoxLayout.addWidget(self.caseInsensitiveOptionCheckBox, gridRow, \ - 1) - self.patternOptionsCheckBoxLayout.addWidget(self.dotMatchesEverythingOptionCheckBox, gridRow\ - ,2) + self.patternOptionsCheckBoxLayout.addWidget(self.caseInsensitiveOptionCheckBox, gridRow, 1) + self.patternOptionsCheckBoxLayout.addWidget( + self.dotMatchesEverythingOptionCheckBox, gridRow, 2) gridRow = gridRow + 1 - self.patternOptionsCheckBoxLayout.addWidget(self.multilineOptionCheckBox, gridRow, \ - 1) - self.patternOptionsCheckBoxLayout.addWidget(self.extendedPatternSyntaxOptionCheckBox, gridRow \ - , 2) + self.patternOptionsCheckBoxLayout.addWidget(self.multilineOptionCheckBox, gridRow, 1) + self.patternOptionsCheckBoxLayout.addWidget( + self.extendedPatternSyntaxOptionCheckBox, gridRow, 2) gridRow = gridRow + 1 - self.patternOptionsCheckBoxLayout.addWidget(self.invertedGreedinessOptionCheckBox, gridRow,\ - 1) - self.patternOptionsCheckBoxLayout.addWidget(self.dontCaptureOptionCheckBox, gridRow,\ - 2) + self.patternOptionsCheckBoxLayout.addWidget( + self.invertedGreedinessOptionCheckBox, gridRow, 1) + self.patternOptionsCheckBoxLayout.addWidget(self.dontCaptureOptionCheckBox, gridRow, 2) gridRow = gridRow + 1 - self.patternOptionsCheckBoxLayout.addWidget(self.useUnicodePropertiesOptionCheckBox, gridRow,\ - 1) + self.patternOptionsCheckBoxLayout.addWidget( + self.useUnicodePropertiesOptionCheckBox, gridRow, 1) form_layout.addRow("Pattern options:", self.patternOptionsCheckBoxLayout) diff --git a/examples/widgets/tutorials/addressbook/part2.py b/examples/widgets/tutorials/addressbook/part2.py index 89c81300..3c0eb451 100644 --- a/examples/widgets/tutorials/addressbook/part2.py +++ b/examples/widgets/tutorials/addressbook/part2.py @@ -102,17 +102,16 @@ class AddressBook(QWidget): address = self._address_text.toPlainText() if name == "" or address == "": - QMessageBox.information(self, "Empty Field", - "Please enter a name and address.") + QMessageBox.information(self, "Empty Field", "Please enter a name and address.") return if name not in self.contacts: self.contacts[name] = address QMessageBox.information(self, "Add Successful", - f'"{name}" has been added to your address book.') + f'"{name}" has been added to your address book.') else: QMessageBox.information(self, "Add Unsuccessful", - f'Sorry, "{name}" is already in your address book.') + f'Sorry, "{name}" is already in your address book.') return if not self.contacts: diff --git a/examples/widgets/tutorials/addressbook/part3.py b/examples/widgets/tutorials/addressbook/part3.py index 571a96a4..611796f5 100644 --- a/examples/widgets/tutorials/addressbook/part3.py +++ b/examples/widgets/tutorials/addressbook/part3.py @@ -116,17 +116,16 @@ class AddressBook(QWidget): address = self._address_text.toPlainText() if name == "" or address == "": - QMessageBox.information(self, "Empty Field", - "Please enter a name and address.") + QMessageBox.information(self, "Empty Field", "Please enter a name and address.") return if name not in self.contacts: self.contacts[name] = address QMessageBox.information(self, "Add Successful", - f'"{name}" has been added to your address book.') + f'"{name}" has been added to your address book.') else: QMessageBox.information(self, "Add Unsuccessful", - f'Sorry, "{name}" is already in your address book.') + f'Sorry, "{name}" is already in your address book.') return if not self.contacts: diff --git a/examples/widgets/tutorials/addressbook/part4.py b/examples/widgets/tutorials/addressbook/part4.py index 505fe4db..95f31d46 100644 --- a/examples/widgets/tutorials/addressbook/part4.py +++ b/examples/widgets/tutorials/addressbook/part4.py @@ -128,34 +128,34 @@ class AddressBook(QWidget): address = self._address_text.toPlainText() if name == "" or address == "": - QMessageBox.information(self, "Empty Field", - "Please enter a name and address.") + QMessageBox.information(self, "Empty Field", "Please enter a name and address.") return if self._current_mode == self.AddingMode: if name not in self.contacts: self.contacts[name] = address QMessageBox.information(self, "Add Successful", - f'"{name}" has been added to your address book.') + f'"{name}" has been added to your address book.') else: QMessageBox.information(self, "Add Unsuccessful", - f'Sorry, "{name}" is already in your address book.') + f'Sorry, "{name}" is already in your address book.') return elif self._current_mode == self.EditingMode: if self._old_name != name: if name not in self.contacts: QMessageBox.information(self, "Edit Successful", - f'"{self.oldName}" has been edited in your address book.') + f'"{self.oldName}" has been edited in your ' + 'address book.') del self.contacts[self._old_name] self.contacts[name] = address else: QMessageBox.information(self, "Edit Unsuccessful", - f'Sorry, "{name}" is already in your address book.') + f'Sorry, "{name}" is already in your address book.') return elif self._old_address != address: QMessageBox.information(self, "Edit Successful", - f'"{name}" has been edited in your address book.') + f'"{name}" has been edited in your address book.') self.contacts[name] = address self.update_interface(self.NavigationMode) @@ -169,19 +169,18 @@ class AddressBook(QWidget): @Slot() def remove_contact(self): name = self._name_line.text() - address = self._address_text.toPlainText() if name in self.contacts: button = QMessageBox.question(self, "Confirm Remove", - f'Are you sure you want to remove "{name}"?', - QMessageBox.Yes | QMessageBox.No) + f'Are you sure you want to remove "{name}"?', + QMessageBox.Yes | QMessageBox.No) if button == QMessageBox.Yes: self.previous() del self.contacts[name] QMessageBox.information(self, "Remove Successful", - f'"{name}" has been removed from your address book.') + f'"{name}" has been removed from your address book.') self.update_interface(self.NavigationMode) diff --git a/examples/widgets/tutorials/addressbook/part5.py b/examples/widgets/tutorials/addressbook/part5.py index 72245703..1e9c0586 100644 --- a/examples/widgets/tutorials/addressbook/part5.py +++ b/examples/widgets/tutorials/addressbook/part5.py @@ -134,34 +134,34 @@ class AddressBook(QWidget): address = self._address_text.toPlainText() if name == "" or address == "": - QMessageBox.information(self, "Empty Field", - "Please enter a name and address.") + QMessageBox.information(self, "Empty Field", "Please enter a name and address.") return if self._current_mode == self.AddingMode: if name not in self.contacts: self.contacts[name] = address QMessageBox.information(self, "Add Successful", - f'"{name}" has been added to your address book.') + f'"{name}" has been added to your address book.') else: QMessageBox.information(self, "Add Unsuccessful", - f'Sorry, "{name}" is already in your address book.') + f'Sorry, "{name}" is already in your address book.') return elif self._current_mode == self.EditingMode: if self._old_name != name: if name not in self.contacts: QMessageBox.information(self, "Edit Successful", - f'"{self.oldName}" has been edited in your address book.') + f'"{self.oldName}" has been edited in your ' + 'address book.') del self.contacts[self._old_name] self.contacts[name] = address else: QMessageBox.information(self, "Edit Unsuccessful", - f'Sorry, "{name}" is already in your address book.') + f'Sorry, "{name}" is already in your address book.') return elif self._old_address != address: QMessageBox.information(self, "Edit Successful", - f'"{name}" has been edited in your address book.') + f'"{name}" has been edited in your address book.') self.contacts[name] = address self.update_interface(self.NavigationMode) @@ -175,19 +175,18 @@ class AddressBook(QWidget): @Slot() def remove_contact(self): name = self._name_line.text() - address = self._address_text.toPlainText() if name in self.contacts: button = QMessageBox.question(self, "Confirm Remove", - f'Are you sure you want to remove "{name}"?', - QMessageBox.Yes | QMessageBox.No) + f'Are you sure you want to remove "{name}"?', + QMessageBox.Yes | QMessageBox.No) if button == QMessageBox.Yes: self.previous() del self.contacts[name] QMessageBox.information(self, "Remove Successful", - f'"{name}" has been removed from your address book.') + f'"{name}" has been removed from your address book.') self.update_interface(self.NavigationMode) @@ -243,7 +242,7 @@ class AddressBook(QWidget): self._address_text.setText(self.contacts[contact_name]) else: QMessageBox.information(self, "Contact Not Found", - f'Sorry, "{contact_name}" is not in your address book.') + f'Sorry, "{contact_name}" is not in your address book.') return self.update_interface(self.NavigationMode) @@ -311,8 +310,7 @@ class FindDialog(QDialog): text = self._line_edit.text() if not text: - QMessageBox.information(self, "Empty Field", - "Please enter a name.") + QMessageBox.information(self, "Empty Field", "Please enter a name.") return else: self._find_text = text diff --git a/examples/widgets/tutorials/addressbook/part6.py b/examples/widgets/tutorials/addressbook/part6.py index f75fbf44..d11298fb 100644 --- a/examples/widgets/tutorials/addressbook/part6.py +++ b/examples/widgets/tutorials/addressbook/part6.py @@ -145,34 +145,34 @@ class AddressBook(QWidget): address = self._address_text.toPlainText() if name == "" or address == "": - QMessageBox.information(self, "Empty Field", - "Please enter a name and address.") + QMessageBox.information(self, "Empty Field", "Please enter a name and address.") return if self._current_mode == self.AddingMode: if name not in self.contacts: self.contacts[name] = address QMessageBox.information(self, "Add Successful", - f'"{name}" has been added to your address book.') + f'"{name}" has been added to your address book.') else: QMessageBox.information(self, "Add Unsuccessful", - f'Sorry, "{name}" is already in your address book.') + f'Sorry, "{name}" is already in your address book.') return elif self._current_mode == self.EditingMode: if self._old_name != name: if name not in self.contacts: QMessageBox.information(self, "Edit Successful", - f'"{self.oldName}" has been edited in your address book.') + f'"{self.oldName}" has been edited in your ' + 'address book.') del self.contacts[self._old_name] self.contacts[name] = address else: QMessageBox.information(self, "Edit Unsuccessful", - f'Sorry, "{name}" is already in your address book.') + f'Sorry, "{name}" is already in your address book.') return elif self._old_address != address: QMessageBox.information(self, "Edit Successful", - f'"{name}" has been edited in your address book.') + f'"{name}" has been edited in your address book.') self.contacts[name] = address self.update_interface(self.NavigationMode) @@ -186,19 +186,18 @@ class AddressBook(QWidget): @Slot() def remove_contact(self): name = self._name_line.text() - address = self._address_text.toPlainText() if name in self.contacts: button = QMessageBox.question(self, "Confirm Remove", - f'Are you sure you want to remove "{name}"?', - QMessageBox.Yes | QMessageBox.No) + f'Are you sure you want to remove "{name}"?', + QMessageBox.Yes | QMessageBox.No) if button == QMessageBox.Yes: self.previous() del self.contacts[name] QMessageBox.information(self, "Remove Successful", - f'"{name}" has been removed from your address book.') + f'"{name}" has been removed from your address book.') self.update_interface(self.NavigationMode) @@ -254,7 +253,7 @@ class AddressBook(QWidget): self._address_text.setText(self.contacts[contact_name]) else: QMessageBox.information(self, "Contact Not Found", - f'Sorry, "{contact_name}" is not in your address book.') + f'Sorry, "{contact_name}" is not in your address book.') return self.update_interface(self.NavigationMode) @@ -304,8 +303,8 @@ class AddressBook(QWidget): def save_to_file(self): fileName, _ = QFileDialog.getSaveFileName(self, - "Save Address Book", '', - "Address Book (*.abk);;All Files (*)") + "Save Address Book", '', + "Address Book (*.abk);;All Files (*)") if not fileName: return @@ -314,7 +313,7 @@ class AddressBook(QWidget): out_file = open(str(fileName), 'wb') except IOError: QMessageBox.information(self, "Unable to open file", - f'There was an error opening "{fileName}"') + f'There was an error opening "{fileName}"') return pickle.dump(self.contacts, out_file) @@ -322,8 +321,8 @@ class AddressBook(QWidget): def load_from_file(self): fileName, _ = QFileDialog.getOpenFileName(self, - "Open Address Book", '', - "Address Book (*.abk);;All Files (*)") + "Open Address Book", '', + "Address Book (*.abk);;All Files (*)") if not fileName: return @@ -332,7 +331,7 @@ class AddressBook(QWidget): in_file = open(str(fileName), 'rb') except IOError: QMessageBox.information(self, "Unable to open file", - f'There was an error opening "{fileName}"') + f'There was an error opening "{fileName}"') return self.contacts = pickle.load(in_file) @@ -340,8 +339,7 @@ class AddressBook(QWidget): if len(self.contacts) == 0: QMessageBox.information(self, "No contacts in file", - "The file you are attempting to open contains no " - "contacts.") + "The file you are attempting to open contains no contacts.") else: for name, address in self.contacts: self._name_line.setText(name) @@ -375,8 +373,7 @@ class FindDialog(QDialog): text = self._line_edit.text() if not text: - QMessageBox.information(self, "Empty Field", - "Please enter a name.") + QMessageBox.information(self, "Empty Field", "Please enter a name.") return self._find_text = text diff --git a/examples/widgets/tutorials/addressbook/part7.py b/examples/widgets/tutorials/addressbook/part7.py index 2f874f9b..3829c003 100644 --- a/examples/widgets/tutorials/addressbook/part7.py +++ b/examples/widgets/tutorials/addressbook/part7.py @@ -151,34 +151,34 @@ class AddressBook(QWidget): address = self._address_text.toPlainText() if name == "" or address == "": - QMessageBox.information(self, "Empty Field", - "Please enter a name and address.") + QMessageBox.information(self, "Empty Field", "Please enter a name and address.") return if self._current_mode == self.AddingMode: if name not in self.contacts: self.contacts[name] = address QMessageBox.information(self, "Add Successful", - f'"{name}" has been added to your address book.') + f'"{name}" has been added to your address book.') else: QMessageBox.information(self, "Add Unsuccessful", - f'Sorry, "{name}" is already in your address book.') + f'Sorry, "{name}" is already in your address book.') return elif self._current_mode == self.EditingMode: if self._old_name != name: if name not in self.contacts: QMessageBox.information(self, "Edit Successful", - f'"{self.oldName}" has been edited in your address book.') + f'"{self.oldName}" has been edited in your ' + 'address book.') del self.contacts[self._old_name] self.contacts[name] = address else: QMessageBox.information(self, "Edit Unsuccessful", - f'Sorry, "{name}" is already in your address book.') + f'Sorry, "{name}" is already in your address book.') return elif self._old_address != address: QMessageBox.information(self, "Edit Successful", - f'"{name}" has been edited in your address book.') + f'"{name}" has been edited in your address book.') self.contacts[name] = address self.update_interface(self.NavigationMode) @@ -192,19 +192,18 @@ class AddressBook(QWidget): @Slot() def remove_contact(self): name = self._name_line.text() - address = self._address_text.toPlainText() if name in self.contacts: button = QMessageBox.question(self, "Confirm Remove", - f'Are you sure you want to remove "{name}"?', - QMessageBox.Yes | QMessageBox.No) + f'Are you sure you want to remove "{name}"?', + QMessageBox.Yes | QMessageBox.No) if button == QMessageBox.Yes: self.previous() del self.contacts[name] QMessageBox.information(self, "Remove Successful", - f'"{name}" has been removed from your address book.') + f'"{name}" has been removed from your address book.') self.update_interface(self.NavigationMode) @@ -260,7 +259,7 @@ class AddressBook(QWidget): self._address_text.setText(self.contacts[contact_name]) else: QMessageBox.information(self, "Contact Not Found", - f'Sorry, "{contact_name}" is not in your address book.') + f'Sorry, "{contact_name}" is not in your address book.') return self.update_interface(self.NavigationMode) @@ -313,8 +312,8 @@ class AddressBook(QWidget): def save_to_file(self): fileName, _ = QFileDialog.getSaveFileName(self, - "Save Address Book", '', - "Address Book (*.abk);;All Files (*)") + "Save Address Book", '', + "Address Book (*.abk);;All Files (*)") if not fileName: return @@ -323,7 +322,7 @@ class AddressBook(QWidget): out_file = open(str(fileName), 'wb') except IOError: QMessageBox.information(self, "Unable to open file", - f'There was an error opening "{fileName}"') + f'There was an error opening "{fileName}"') return pickle.dump(self.contacts, out_file) @@ -331,8 +330,8 @@ class AddressBook(QWidget): def load_from_file(self): fileName, _ = QFileDialog.getOpenFileName(self, - "Open Address Book", '', - "Address Book (*.abk);;All Files (*)") + "Open Address Book", '', + "Address Book (*.abk);;All Files (*)") if not fileName: return @@ -341,7 +340,7 @@ class AddressBook(QWidget): in_file = open(str(fileName), 'rb') except IOError: QMessageBox.information(self, "Unable to open file", - f'There was an error opening "{fileName}"') + f'There was an error opening "{fileName}"') return self.contacts = pickle.load(in_file) @@ -349,8 +348,7 @@ class AddressBook(QWidget): if len(self.contacts) == 0: QMessageBox.information(self, "No contacts in file", - "The file you are attempting to open contains no " - "contacts.") + "The file you are attempting to open contains no contacts.") else: for name, address in self.contacts: self._name_line.setText(name) @@ -372,7 +370,7 @@ class AddressBook(QWidget): last_name = '' file_name = QFileDialog.getSaveFileName(self, "Export Contact", - '', "vCard Files (*.vcf);;All Files (*)")[0] + '', "vCard Files (*.vcf);;All Files (*)")[0] if not file_name: return @@ -380,8 +378,7 @@ class AddressBook(QWidget): out_file = QFile(file_name) if not out_file.open(QIODevice.WriteOnly): - QMessageBox.information(self, "Unable to open file", - out_file.errorString()) + QMessageBox.information(self, "Unable to open file", out_file.errorString()) return out_s = QTextStream(out_file) @@ -399,7 +396,7 @@ class AddressBook(QWidget): out_s << 'END:VCARD' << '\n' QMessageBox.information(self, "Export Successful", - f'"{name}" has been exported as a vCard.') + f'"{name}" has been exported as a vCard.') class FindDialog(QDialog): @@ -427,8 +424,7 @@ class FindDialog(QDialog): text = self._line_edit.text() if not text: - QMessageBox.information(self, "Empty Field", - "Please enter a name.") + QMessageBox.information(self, "Empty Field", "Please enter a name.") return self._find_text = text diff --git a/examples/widgets/tutorials/cannon/t10.py b/examples/widgets/tutorials/cannon/t10.py index c9c3fa7f..8649bb56 100644 --- a/examples/widgets/tutorials/cannon/t10.py +++ b/examples/widgets/tutorials/cannon/t10.py @@ -43,8 +43,8 @@ class LCDRange(QWidget): def set_range(self, minValue, maxValue): if minValue < 0 or maxValue > 99 or minValue > maxValue: qWarning(f"LCDRange::setRange({minValue}, {maxValue})\n" - "\tRange must be 0..99\n" - "\tand minValue must not be greater than maxValue") + "\tRange must be 0..99\n" + "\tand minValue must not be greater than maxValue") return self.slider.setRange(minValue, maxValue) @@ -113,7 +113,7 @@ class MyWidget(QWidget): quit = QPushButton("&Quit") quit.setFont(QFont("Times", 18, QFont.Bold)) - quit.clicked.connect(qApp.quit) + quit.clicked.connect(qApp.quit) # noqa: F821 angle = LCDRange() angle.set_range(5, 70) diff --git a/examples/widgets/tutorials/cannon/t11.py b/examples/widgets/tutorials/cannon/t11.py index 997eecbd..fbfd2481 100644 --- a/examples/widgets/tutorials/cannon/t11.py +++ b/examples/widgets/tutorials/cannon/t11.py @@ -45,8 +45,8 @@ class LCDRange(QWidget): def set_range(self, minValue, maxValue): if minValue < 0 or maxValue > 99 or minValue > maxValue: qWarning(f"LCDRange::setRange({minValue}, {maxValue})\n" - "\tRange must be 0..99\n" - "\tand minValue must not be greater than maxValue") + "\tRange must be 0..99\n" + "\tand minValue must not be greater than maxValue") return self.slider.setRange(minValue, maxValue) @@ -175,7 +175,7 @@ class MyWidget(QWidget): quit = QPushButton("&Quit") quit.setFont(QFont("Times", 18, QFont.Bold)) - quit.clicked.connect(qApp.quit) + quit.clicked.connect(qApp.quit) # noqa: F821 angle = LCDRange() angle.set_range(5, 70) diff --git a/examples/widgets/tutorials/cannon/t12.py b/examples/widgets/tutorials/cannon/t12.py index 8847b220..749c2468 100644 --- a/examples/widgets/tutorials/cannon/t12.py +++ b/examples/widgets/tutorials/cannon/t12.py @@ -62,8 +62,8 @@ class LCDRange(QWidget): def set_range(self, minValue, maxValue): if minValue < 0 or maxValue > 99 or minValue > maxValue: qWarning(f"LCDRange::setRange({minValue}, {maxValue})\n" - "\tRange must be 0..99\n" - "\tand minValue must not be greater than maxValue") + "\tRange must be 0..99\n" + "\tand minValue must not be greater than maxValue") return self.slider.setRange(minValue, maxValue) @@ -226,7 +226,7 @@ class MyWidget(QWidget): quit = QPushButton("&Quit") quit.setFont(QFont("Times", 18, QFont.Bold)) - quit.clicked.connect(qApp.quit) + quit.clicked.connect(qApp.quit) # noqa: F821 angle = LCDRange("ANGLE") angle.set_range(5, 70) diff --git a/examples/widgets/tutorials/cannon/t13.py b/examples/widgets/tutorials/cannon/t13.py index 4eb45a37..f9a771d1 100644 --- a/examples/widgets/tutorials/cannon/t13.py +++ b/examples/widgets/tutorials/cannon/t13.py @@ -64,8 +64,8 @@ class LCDRange(QWidget): def set_range(self, minValue, maxValue): if minValue < 0 or maxValue > 99 or minValue > maxValue: qWarning(f"LCDRange::setRange({minValue}, {maxValue})\n" - "\tRange must be 0..99\n" - "\tand minValue must not be greater than maxValue") + "\tRange must be 0..99\n" + "\tand minValue must not be greater than maxValue") return self.slider.setRange(minValue, maxValue) @@ -259,7 +259,7 @@ class GameBoard(QWidget): quit = QPushButton("&Quit") quit.setFont(QFont("Times", 18, QFont.Bold)) - quit.clicked.connect(qApp.quit) + quit.clicked.connect(qApp.quit) # noqa: F821 angle = LCDRange("ANGLE") angle.set_range(5, 70) diff --git a/examples/widgets/tutorials/cannon/t14.py b/examples/widgets/tutorials/cannon/t14.py index e4f1c350..3c94408f 100644 --- a/examples/widgets/tutorials/cannon/t14.py +++ b/examples/widgets/tutorials/cannon/t14.py @@ -65,8 +65,8 @@ class LCDRange(QWidget): def set_range(self, minValue, maxValue): if minValue < 0 or maxValue > 99 or minValue > maxValue: qWarning(f"LCDRange::setRange({minValue}, {maxValue})\n" - "\tRange must be 0..99\n" - "\tand minValue must not be greater than maxValue") + "\tRange must be 0..99\n" + "\tand minValue must not be greater than maxValue") return self.slider.setRange(minValue, maxValue) @@ -174,7 +174,8 @@ class CannonField(QWidget): self._auto_shoot_timer.stop() self.hit.emit() self.can_shoot.emit(True) - elif shot_r.x() > self.width() or shot_r.y() > self.height() or shot_r.intersects(self.barrier_rect()): + elif (shot_r.x() > self.width() or shot_r.y() > self.height() + or shot_r.intersects(self.barrier_rect())): self._auto_shoot_timer.stop() self.missed.emit() self.can_shoot.emit(True) @@ -301,7 +302,7 @@ class GameBoard(QWidget): quit = QPushButton("&Quit") quit.setFont(QFont("Times", 18, QFont.Bold)) - quit.clicked.connect(qApp.quit) + quit.clicked.connect(qApp.quit) # noqa: F821 angle = LCDRange("ANGLE") angle.set_range(5, 70) diff --git a/examples/widgets/tutorials/cannon/t4.py b/examples/widgets/tutorials/cannon/t4.py index ba0ebc41..37a2dc9d 100644 --- a/examples/widgets/tutorials/cannon/t4.py +++ b/examples/widgets/tutorials/cannon/t4.py @@ -20,7 +20,7 @@ class MyWidget(QWidget): self.quit.setGeometry(62, 40, 75, 30) self.quit.setFont(QFont("Times", 18, QFont.Bold)) - self.quit.clicked.connect(qApp.quit) + self.quit.clicked.connect(qApp.quit) # noqa: F821 if __name__ == '__main__': diff --git a/examples/widgets/tutorials/cannon/t5.py b/examples/widgets/tutorials/cannon/t5.py index 42faeed0..ed5d085f 100644 --- a/examples/widgets/tutorials/cannon/t5.py +++ b/examples/widgets/tutorials/cannon/t5.py @@ -25,7 +25,7 @@ class MyWidget(QWidget): slider.setRange(0, 99) slider.setValue(0) - quit.clicked.connect(qApp.quit) + quit.clicked.connect(qApp.quit) # noqa: F821 slider.valueChanged.connect(lcd.display) layout = QVBoxLayout(self) diff --git a/examples/widgets/tutorials/cannon/t6.py b/examples/widgets/tutorials/cannon/t6.py index 1cc2906f..ea2e044e 100644 --- a/examples/widgets/tutorials/cannon/t6.py +++ b/examples/widgets/tutorials/cannon/t6.py @@ -33,7 +33,7 @@ class MyWidget(QWidget): quit = QPushButton("Quit") quit.setFont(QFont("Times", 18, QFont.Bold)) - quit.clicked.connect(qApp.quit) + quit.clicked.connect(qApp.quit) # noqa: F821 layout = QVBoxLayout(self) layout.addWidget(quit) diff --git a/examples/widgets/tutorials/cannon/t7.py b/examples/widgets/tutorials/cannon/t7.py index 51128e6c..1175107b 100644 --- a/examples/widgets/tutorials/cannon/t7.py +++ b/examples/widgets/tutorials/cannon/t7.py @@ -46,7 +46,7 @@ class MyWidget(QWidget): quit = QPushButton("Quit") quit.setFont(QFont("Times", 18, QFont.Bold)) - quit.clicked.connect(qApp.quit) + quit.clicked.connect(qApp.quit) # noqa: F821 previous_range = None diff --git a/examples/widgets/tutorials/cannon/t8.py b/examples/widgets/tutorials/cannon/t8.py index b82e24a0..9bb5516b 100644 --- a/examples/widgets/tutorials/cannon/t8.py +++ b/examples/widgets/tutorials/cannon/t8.py @@ -43,8 +43,8 @@ class LCDRange(QWidget): def set_range(self, minValue, maxValue): if minValue < 0 or maxValue > 99 or minValue > maxValue: qWarning("LCDRange.setRange({minValue}, {maxValue})\n" - "\tRange must be 0..99\n" - "\tand minValue must not be greater than maxValue") + "\tRange must be 0..99\n" + "\tand minValue must not be greater than maxValue") return self.slider.setRange(minValue, maxValue) @@ -88,7 +88,7 @@ class MyWidget(QWidget): quit = QPushButton("Quit") quit.setFont(QFont("Times", 18, QFont.Bold)) - quit.clicked.connect(qApp.quit) + quit.clicked.connect(qApp.quit) # noqa: F821 angle = LCDRange() angle.set_range(5, 70) diff --git a/examples/widgets/tutorials/cannon/t9.py b/examples/widgets/tutorials/cannon/t9.py index 297e98e5..7cdda4e7 100644 --- a/examples/widgets/tutorials/cannon/t9.py +++ b/examples/widgets/tutorials/cannon/t9.py @@ -43,8 +43,8 @@ class LCDRange(QWidget): def set_range(self, minValue, maxValue): if minValue < 0 or maxValue > 99 or minValue > maxValue: qWarning(f"LCDRange::setRange({minValue}, {maxValue})\n" - "\tRange must be 0..99\n" - "\tand minValue must not be greater than maxValue") + "\tRange must be 0..99\n" + "\tand minValue must not be greater than maxValue") return self.slider.setRange(minValue, maxValue) @@ -94,7 +94,7 @@ class MyWidget(QWidget): quit = QPushButton("Quit") quit.setFont(QFont("Times", 18, QFont.Bold)) - quit.clicked.connect(qApp.quit) + quit.clicked.connect(qApp.quit) # noqa: F821 angle = LCDRange() angle.set_range(5, 70) diff --git a/examples/widgets/tutorials/modelview/2_formatting.py b/examples/widgets/tutorials/modelview/2_formatting.py index 73c993e5..f39ec462 100644 --- a/examples/widgets/tutorials/modelview/2_formatting.py +++ b/examples/widgets/tutorials/modelview/2_formatting.py @@ -32,7 +32,7 @@ class MyModel(QAbstractTableModel): return "<--left" if row == 1 and col == 1: return "right-->" - return f"Row{row}, Column{col+1}" + return f"Row{row}, Column{col + 1}" elif role == Qt.FontRole: if row == 0 and col == 0: # change font only for cell(0,0) diff --git a/examples/widgets/tutorials/modelview/3_changingmodel.py b/examples/widgets/tutorials/modelview/3_changingmodel.py index e277dd1e..2148ec5d 100644 --- a/examples/widgets/tutorials/modelview/3_changingmodel.py +++ b/examples/widgets/tutorials/modelview/3_changingmodel.py @@ -43,6 +43,7 @@ class MyModel(QAbstractTableModel): self.dataChanged.emit(top_left, top_left, [Qt.DisplayRole]) #! [3] + if __name__ == '__main__': app = QApplication(sys.argv) table_view = QTableView() diff --git a/examples/widgets/tutorials/modelview/6_treeview.py b/examples/widgets/tutorials/modelview/6_treeview.py index 09300560..cac3c6d5 100644 --- a/examples/widgets/tutorials/modelview/6_treeview.py +++ b/examples/widgets/tutorials/modelview/6_treeview.py @@ -8,6 +8,7 @@ from PySide6.QtWidgets import QApplication, QMainWindow, QTreeView """PySide6 port of the widgets/tutorials/modelview/6_treeview example from Qt v6.x""" + #! [1] class MainWindow(QMainWindow): def __init__(self, parent=None): diff --git a/examples/widgets/tutorials/modelview/7_selections.py b/examples/widgets/tutorials/modelview/7_selections.py index 6c519c86..c879d8f6 100644 --- a/examples/widgets/tutorials/modelview/7_selections.py +++ b/examples/widgets/tutorials/modelview/7_selections.py @@ -9,6 +9,7 @@ from PySide6.QtWidgets import QApplication, QMainWindow, QTreeView """PySide6 port of the widgets/tutorials/modelview/7_selections example from Qt v6.x""" + #! [1] class MainWindow(QMainWindow): def __init__(self, parent=None): diff --git a/examples/widgets/widgets/charactermap/characterwidget.py b/examples/widgets/widgets/charactermap/characterwidget.py index 1df2a3b7..0f01f968 100644 --- a/examples/widgets/widgets/charactermap/characterwidget.py +++ b/examples/widgets/widgets/charactermap/characterwidget.py @@ -127,7 +127,7 @@ class CharacterWidget(QWidget): self._square_size, self._square_size, QBrush(Qt.red)) text = chr(key) - painter.drawText(column * self._square_size + (self._square_size / 2) - - font_metrics.horizontalAdvance(text) / 2, + painter.drawText(column * self._square_size + (self._square_size / 2) + - font_metrics.horizontalAdvance(text) / 2, row * self._square_size + 4 + font_metrics.ascent(), text) diff --git a/examples/widgets/widgets/charactermap/mainwindow.py b/examples/widgets/widgets/charactermap/mainwindow.py index 5f0e2bce..d79285de 100644 --- a/examples/widgets/widgets/charactermap/mainwindow.py +++ b/examples/widgets/widgets/charactermap/mainwindow.py @@ -30,7 +30,7 @@ class MainWindow(QMainWindow): file_menu.addAction("Quit", self.close) help_menu = self.menuBar().addMenu("Help") help_menu.addAction("Show Font Info", self.show_info) - help_menu.addAction("About &Qt", qApp.aboutQt) + help_menu.addAction("About &Qt", qApp.aboutQt) # noqa: F821 central_widget = QWidget() diff --git a/examples/widgets/widgets/digitalclock/doc/digitalclock.rst b/examples/widgets/widgets/digitalclock/doc/digitalclock.rst index ef800d9c..d13275d2 100644 --- a/examples/widgets/widgets/digitalclock/doc/digitalclock.rst +++ b/examples/widgets/widgets/digitalclock/doc/digitalclock.rst @@ -1,6 +1,8 @@ Digital Clock Example ===================== +.. tags:: Android + The Digital Clock example shows how to use QLCDNumber to display a number with LCD-like digits. diff --git a/examples/widgets/widgets/tetrix/tetrix.py b/examples/widgets/widgets/tetrix/tetrix.py index 36cbb344..b5df2aa3 100644 --- a/examples/widgets/widgets/tetrix/tetrix.py +++ b/examples/widgets/widgets/tetrix/tetrix.py @@ -52,7 +52,7 @@ class TetrixWindow(QWidget): start_button.clicked.connect(self.board.start) pause_button.clicked.connect(self.board.pause) - quit_button.clicked.connect(qApp.quit) + quit_button.clicked.connect(qApp.quit) # noqa: F821 self.board.score_changed.connect(score_lcd.display) self.board.level_changed.connect(level_lcd.display) self.board.lines_removed_changed.connect(lines_lcd.display) @@ -134,11 +134,11 @@ class TetrixBoard(QFrame): def sizeHint(self): return QSize(TetrixBoard.board_width * 15 + self.frameWidth() * 2, - TetrixBoard.board_height * 15 + self.frameWidth() * 2) + TetrixBoard.board_height * 15 + self.frameWidth() * 2) def minimum_size_hint(self): return QSize(TetrixBoard.board_width * 5 + self.frameWidth() * 2, - TetrixBoard.board_height * 5 + self.frameWidth() * 2) + TetrixBoard.board_height * 5 + self.frameWidth() * 2) @Slot() def start(self): @@ -190,16 +190,17 @@ class TetrixBoard(QFrame): shape = self.shape_at(j, TetrixBoard.board_height - i - 1) if shape != Piece.NoShape: self.draw_square(painter, - rect.left() + j * self.square_width(), - board_top + i * self.square_height(), shape) + rect.left() + j * self.square_width(), + board_top + i * self.square_height(), shape) if self._cur_piece.shape() != Piece.NoShape: for i in range(4): x = self._cur_x + self._cur_piece.x(i) y = self._cur_y - self._cur_piece.y(i) self.draw_square(painter, rect.left() + x * self.square_width(), - board_top + (TetrixBoard.board_height - y - 1) * self.square_height(), - self._cur_piece.shape()) + board_top + + (TetrixBoard.board_height - y - 1) * self.square_height(), + self._cur_piece.shape()) def keyPressEvent(self, event): if not self._is_started or self._is_paused or self._cur_piece.shape() == Piece.NoShape: @@ -234,7 +235,8 @@ class TetrixBoard(QFrame): super(TetrixBoard, self).timerEvent(event) def clear_board(self): - self.board = [Piece.NoShape for i in range(TetrixBoard.board_height * TetrixBoard.board_width)] + self.board = [ + Piece.NoShape for _ in range(TetrixBoard.board_height * TetrixBoard.board_width)] def drop_down(self): drop_height = 0 @@ -328,7 +330,7 @@ class TetrixBoard(QFrame): x = self._next_piece.x(i) - self._next_piece.min_x() y = self._next_piece.y(i) - self._next_piece.min_y() self.draw_square(painter, x * self.square_width(), - y * self.square_height(), self._next_piece.shape()) + y * self.square_height(), self._next_piece.shape()) self.nextPieceLabel.setPixmap(pixmap) @@ -349,11 +351,10 @@ class TetrixBoard(QFrame): def draw_square(self, painter, x, y, shape): color_table = [0x000000, 0xCC6666, 0x66CC66, 0x6666CC, - 0xCCCC66, 0xCC66CC, 0x66CCCC, 0xDAAA00] + 0xCCCC66, 0xCC66CC, 0x66CCCC, 0xDAAA00] color = QColor(color_table[shape]) - painter.fillRect(x + 1, y + 1, self.square_width() - 2, - self.square_height() - 2, color) + painter.fillRect(x + 1, y + 1, self.square_width() - 2, self.square_height() - 2, color) painter.setPen(color.lighter()) painter.drawLine(x, y + self.square_height() - 1, x, y) @@ -361,25 +362,25 @@ class TetrixBoard(QFrame): painter.setPen(color.darker()) painter.drawLine(x + 1, y + self.square_height() - 1, - x + self.square_width() - 1, y + self.square_height() - 1) + x + self.square_width() - 1, y + self.square_height() - 1) painter.drawLine(x + self.square_width() - 1, - y + self.square_height() - 1, x + self.square_width() - 1, y + 1) + y + self.square_height() - 1, x + self.square_width() - 1, y + 1) class TetrixPiece(object): coords_table = ( - ((0, 0), (0, 0), (0, 0), (0, 0)), - ((0, -1), (0, 0), (-1, 0), (-1, 1)), - ((0, -1), (0, 0), (1, 0), (1, 1)), - ((0, -1), (0, 0), (0, 1), (0, 2)), - ((-1, 0), (0, 0), (1, 0), (0, 1)), - ((0, 0), (1, 0), (0, 1), (1, 1)), - ((-1, -1), (0, -1), (0, 0), (0, 1)), - ((1, -1), (0, -1), (0, 0), (0, 1)) + ((0, 0), (0, 0), (0, 0), (0, 0)), + ((0, -1), (0, 0), (-1, 0), (-1, 1)), + ((0, -1), (0, 0), (1, 0), (1, 1)), + ((0, -1), (0, 0), (0, 1), (0, 2)), + ((-1, 0), (0, 0), (1, 0), (0, 1)), + ((0, 0), (1, 0), (0, 1), (1, 1)), + ((-1, -1), (0, -1), (0, 0), (0, 1)), + ((1, -1), (0, -1), (0, 0), (0, 1)) ) def __init__(self): - self.coords = [[0,0] for _ in range(4)] + self.coords = [[0, 0] for _ in range(4)] self._piece_shape = Piece.NoShape self.set_shape(Piece.NoShape) diff --git a/examples/widgets/widgetsgallery/widgetgallery.py b/examples/widgets/widgetsgallery/widgetgallery.py index bf1f523e..d43ab26a 100644 --- a/examples/widgets/widgetsgallery/widgetgallery.py +++ b/examples/widgets/widgetsgallery/widgetgallery.py @@ -164,8 +164,8 @@ class WidgetGallery(QDialog): top_layout.addStretch(1) top_layout.addWidget(disable_widgets_checkbox) - dialog_buttonbox = QDialogButtonBox(QDialogButtonBox.Help | - QDialogButtonBox.Close) + dialog_buttonbox = QDialogButtonBox(QDialogButtonBox.Help + | QDialogButtonBox.Close) init_widget(dialog_buttonbox, "dialogButtonBox") dialog_buttonbox.helpRequested.connect(launch_module_help) dialog_buttonbox.rejected.connect(self.reject) diff --git a/examples/xml/dombookmarks/dombookmarks.py b/examples/xml/dombookmarks/dombookmarks.py index 4fbc843e..a35aeb0f 100644 --- a/examples/xml/dombookmarks/dombookmarks.py +++ b/examples/xml/dombookmarks/dombookmarks.py @@ -7,7 +7,7 @@ import sys from PySide6.QtCore import QDir, QFile, Qt, QTextStream -from PySide6.QtGui import QAction, QIcon +from PySide6.QtGui import QAction, QIcon, QKeySequence from PySide6.QtWidgets import (QApplication, QFileDialog, QHeaderView, QMainWindow, QMessageBox, QStyle, QTreeWidget, QTreeWidgetItem) @@ -30,8 +30,8 @@ class MainWindow(QMainWindow): def open(self): file_name = QFileDialog.getOpenFileName(self, - "Open Bookmark File", QDir.currentPath(), - "XBEL Files (*.xbel *.xml)")[0] + "Open Bookmark File", QDir.currentPath(), + "XBEL Files (*.xbel *.xml)")[0] if not file_name: return @@ -40,7 +40,7 @@ class MainWindow(QMainWindow): if not in_file.open(QFile.ReadOnly | QFile.Text): reason = in_file.errorString() QMessageBox.warning(self, "DOM Bookmarks", - f"Cannot read file {file_name}:\n{reason}.") + f"Cannot read file {file_name}:\n{reason}.") return if self._xbel_tree.read(in_file): @@ -48,8 +48,8 @@ class MainWindow(QMainWindow): def save_as(self): file_name = QFileDialog.getSaveFileName(self, - "Save Bookmark File", QDir.currentPath(), - "XBEL Files (*.xbel *.xml)")[0] + "Save Bookmark File", QDir.currentPath(), + "XBEL Files (*.xbel *.xml)")[0] if not file_name: return @@ -58,7 +58,7 @@ class MainWindow(QMainWindow): if not out_file.open(QFile.WriteOnly | QFile.Text): reason = out_file.errorString() QMessageBox.warning(self, "DOM Bookmarks", - "Cannot write file {fileName}:\n{reason}.") + f"Cannot write file {file_name}:\n{reason}.") return if self._xbel_tree.write(out_file): @@ -66,25 +66,26 @@ class MainWindow(QMainWindow): def about(self): QMessageBox.about(self, "About DOM Bookmarks", - "The DOM Bookmarks example demonstrates how to use Qt's " - "DOM classes to read and write XML documents.") + "The DOM Bookmarks example demonstrates how to use Qt's " + "DOM classes to read and write XML documents.") def create_menus(self): self._file_menu = self.menuBar().addMenu("&File") self._file_menu.addAction(QAction("&Open...", self, - shortcut=QKeySequence(Qt.CTRL | Qt.Key_O), triggered=self.open)) + shortcut=QKeySequence( + Qt.CTRL | Qt.Key_O), triggered=self.open)) self._file_menu.addAction(QAction("&Save As...", self, - shortcut=QKeySequence(Qt.CTRL | Qt.Key_S), triggered=self.save_as)) + shortcut=QKeySequence( + Qt.CTRL | Qt.Key_S), triggered=self.save_as)) self._file_menu.addAction(QAction("E&xit", self, - shortcut=QKeySequence(Qt.CTRL | Qt.Key_Q), triggered=self.close)) + shortcut=QKeySequence( + Qt.CTRL | Qt.Key_Q), triggered=self.close)) self.menuBar().addSeparator() self._help_menu = self.menuBar().addMenu("&Help") - self._help_menu.addAction(QAction("&About", self, - triggered=self.about)) - self._help_menu.addAction(QAction("About &Qt", self, - triggered=qApp.aboutQt)) + self._help_menu.addAction(QAction("&About", self, triggered=self.about)) + self._help_menu.addAction(QAction("About &Qt", self, triggered=qApp.aboutQt)) # noqa: F821 class XbelTree(QTreeWidget): @@ -102,26 +103,27 @@ class XbelTree(QTreeWidget): self._bookmark_icon = QIcon() self._folder_icon.addPixmap(self.style().standardPixmap(QStyle.SP_DirClosedIcon), - QIcon.Normal, QIcon.Off) + QIcon.Normal, QIcon.Off) self._folder_icon.addPixmap(self.style().standardPixmap(QStyle.SP_DirOpenIcon), - QIcon.Normal, QIcon.On) + QIcon.Normal, QIcon.On) self._bookmark_icon.addPixmap(self.style().standardPixmap(QStyle.SP_FileIcon)) def read(self, device): ok, errorStr, errorLine, errorColumn = self._dom_document.setContent(device, True) if not ok: QMessageBox.information(self.window(), "DOM Bookmarks", - f"Parse error at line {errorLine}, column {errorColumn}:\n{errorStr}") + f"Parse error at line {errorLine}, " + f"column {errorColumn}:\n{errorStr}") return False root = self._dom_document.documentElement() if root.tagName() != 'xbel': QMessageBox.information(self.window(), "DOM Bookmarks", - "The file is not an XBEL file.") + "The file is not an XBEL file.") return False elif root.hasAttribute('version') and root.attribute('version') != '1.0': QMessageBox.information(self.window(), "DOM Bookmarks", - "The file is not an XBEL version 1.0 file.") + "The file is not an XBEL version 1.0 file.") return False self.clear() diff --git a/requirements-doc.txt b/requirements-doc.txt index 174a8878..70eb85bf 100644 --- a/requirements-doc.txt +++ b/requirements-doc.txt @@ -1,6 +1,11 @@ sphinx==7.2.6 sphinx-design==0.5.0 sphinx-copybutton==0.5.2 +sphinx-tags==0.3.1 myst-parser==2.0.0 -furo==2023.9.10 +# FIXME: Using fork in order to enable the 'collapse_navbar=True' +# option for the sphinx-theme. Upstream proposal: +# https://github.com/pradyunsg/furo/pull/748#issuecomment-1895448722 +# furo==2023.9.10 +furo @ git+https://github.com/cmaureir/furo@add_collapse graphviz==0.20 diff --git a/requirements.txt b/requirements.txt index 8a3d9912..2b279db3 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,16 +1,16 @@ -setuptools==67.8.0 -packaging -build==0.7 -six -wheel>=0.35 +# Build dependencies +setuptools==69.0.3 +packaging==23.2 +build==1.0.3 +wheel==0.42.0 +distro==1.9.0; sys_platform == 'linux' +patchelf==0.17.2; sys_platform == 'linux' +# 1.24.4 is the last version that supports Python 3.8 +numpy<1.25; python_version < '3.9' +numpy==1.26.3; python_version >= '3.9' + +# For examples PyOpenGL + +# For tests pyinstaller==3.6; platform_machine != 'aarch64' -numpy -nuitka==1.4.8; platform_machine != 'aarch64' -distro; sys_platform == 'linux' -patchelf==0.15; sys_platform == 'linux' -pkginfo; sys_platform == 'linux' -jinja2; sys_platform == 'linux' -buildozer==1.5.0; sys_platform == 'linux' -tqdm; sys_platform == 'linux' -gitpython; sys_platform == 'linux' diff --git a/sources/pyside-tools/CMakeLists.txt b/sources/pyside-tools/CMakeLists.txt index 795ff669..d8a35330 100644 --- a/sources/pyside-tools/CMakeLists.txt +++ b/sources/pyside-tools/CMakeLists.txt @@ -25,7 +25,8 @@ else() ${CMAKE_CURRENT_SOURCE_DIR}/qml.py ${CMAKE_CURRENT_SOURCE_DIR}/qtpy2cpp.py ${CMAKE_CURRENT_SOURCE_DIR}/deploy.py - ${CMAKE_CURRENT_SOURCE_DIR}/android_deploy.py) + ${CMAKE_CURRENT_SOURCE_DIR}/android_deploy.py + ${CMAKE_CURRENT_SOURCE_DIR}/requirements-android.txt) set(directories ${CMAKE_CURRENT_SOURCE_DIR}/deploy_lib ${CMAKE_CURRENT_SOURCE_DIR}/project) diff --git a/sources/pyside-tools/android_deploy.py b/sources/pyside-tools/android_deploy.py index 8ac8aa51..8ef59781 100644 --- a/sources/pyside-tools/android_deploy.py +++ b/sources/pyside-tools/android_deploy.py @@ -8,11 +8,11 @@ import traceback from pathlib import Path from textwrap import dedent -from deploy_lib import (MAJOR_VERSION, cleanup, config_option_exists, - find_pyside_modules, get_config, - install_python_dependencies, setup_python) -from deploy_lib.android import (AndroidData, Buildozer, extract_and_copy_jar, - get_wheel_android_arch) +from deploy_lib import (setup_python, create_config_file, cleanup, install_python_dependencies, + config_option_exists, MAJOR_VERSION) +from deploy_lib.android import AndroidData, AndroidConfig +from deploy_lib.android.buildozer import Buildozer + """ pyside6-android-deploy deployment tool @@ -67,8 +67,8 @@ HELP_EXTRA_MODULES = dedent(""" """) -def main(name: str = None, pyside_wheel: Path = None, shiboken_wheel: Path = None, ndk_path: Path = None, - sdk_path: Path = None, config_file: Path = None, init: bool = False, +def main(name: str = None, pyside_wheel: Path = None, shiboken_wheel: Path = None, + ndk_path: Path = None, sdk_path: Path = None, config_file: Path = None, init: bool = False, loglevel=logging.WARNING, dry_run: bool = False, keep_deployment_files: bool = False, force: bool = False, extra_ignore_dirs: str = None, extra_modules_grouped: str = None): @@ -87,7 +87,6 @@ def main(name: str = None, pyside_wheel: Path = None, shiboken_wheel: Path = Non extra_modules.append(extra_module) main_file = Path.cwd() / "main.py" - generated_files_path = None if not main_file.exists(): raise RuntimeError(("[DEPLOY] For Android deployment to work, the main" " entrypoint Python file should be named 'main.py'" @@ -98,16 +97,25 @@ def main(name: str = None, pyside_wheel: Path = None, shiboken_wheel: Path = Non ndk_path=ndk_path, sdk_path=sdk_path) python = setup_python(dry_run=dry_run, force=force, init=init) - config = get_config(python_exe=python.exe, dry_run=dry_run, config_file=config_file, - main_file=main_file, android_data=android_data, is_android=True) + + config_file_exists = config_file and Path(config_file).exists() + + if config_file_exists: + logging.info(f"[DEPLOY] Using existing config file {config_file}") + else: + config_file = create_config_file(dry_run=dry_run, config_file=config_file, + main_file=main_file) + + config = AndroidConfig(config_file=config_file, source_file=main_file, + python_exe=python.exe, dry_run=dry_run, android_data=android_data, + existing_config_file=config_file_exists, + extra_ignore_dirs=extra_ignore_dirs) if not config.wheel_pyside and not config.wheel_shiboken: raise RuntimeError(f"[DEPLOY] No PySide{MAJOR_VERSION} and Shiboken{MAJOR_VERSION} wheels" "found") - source_file = config.project_dir / config.source_file - generated_files_path = source_file.parent / "deployment" - cleanup(generated_files_path=generated_files_path, config=config, is_android=True) + cleanup(config=config, is_android=True) install_python_dependencies(config=config, python=python, init=init, packages="android_packages", is_android=True) @@ -117,29 +125,11 @@ def main(name: str = None, pyside_wheel: Path = None, shiboken_wheel: Path = Non config.title = name try: - # check which modules are needed - if not config.modules: - config.modules = find_pyside_modules(project_dir=config.project_dir, - extra_ignore_dirs=extra_ignore_dirs, - project_data=config.project_data) - logging.info("The following PySide modules were found from the python files of " - f"the project {config.modules}") - config.modules.extend(extra_modules) - - # extract out and copy .jar files to {generated_files_path} - if not config.jars_dir or not Path(config.jars_dir).exists() and not dry_run: - logging.info("[DEPLOY] Extract and copy jar files from PySide6 wheel to " - f"{generated_files_path}") - config.jars_dir = extract_and_copy_jar(wheel_path=config.wheel_pyside, - generated_files_path=generated_files_path) - - # find architecture from wheel name - if not config.arch: - arch = get_wheel_android_arch(wheel=config.wheel_pyside) - if not arch: - raise RuntimeError("[DEPLOY] PySide wheel corrupted. Wheel name should end with" - "platform name") - config.arch = arch + config.modules += extra_modules + + # this cannot be done when config file is initialized because cleanup() removes it + # so this can only be done after the cleanup() + config.find_and_set_jars_dir() # TODO: include qml files from pysidedeploy.spec rather than from extensions # buildozer currently includes all the files with .qml extension @@ -147,7 +137,7 @@ def main(name: str = None, pyside_wheel: Path = None, shiboken_wheel: Path = Non # init buildozer Buildozer.dry_run = dry_run logging.info("[DEPLOY] Creating buildozer.spec file") - Buildozer.initialize(pysidedeploy_config=config, generated_files_path=generated_files_path) + Buildozer.initialize(pysidedeploy_config=config) # writing config file if not dry_run: @@ -167,17 +157,16 @@ def main(name: str = None, pyside_wheel: Path = None, shiboken_wheel: Path = Non buildozer_build_dir = config.project_dir / ".buildozer" if not buildozer_build_dir.exists(): logging.info(f"[DEPLOY] Unable to copy {buildozer_build_dir} to " - f"{generated_files_path}. {buildozer_build_dir} does not exist") - logging.info(f"[DEPLOY] Copying {str(buildozer_build_dir)} to " - f"{str(generated_files_path)}") - shutil.move(buildozer_build_dir, generated_files_path) + f"{config.generated_files_path}. {buildozer_build_dir} does not exist") + logging.info(f"[DEPLOY] copy {buildozer_build_dir} to {config.generated_files_path}") + shutil.move(buildozer_build_dir, config.generated_files_path) logging.info(f"[DEPLOY] apk created in {config.exe_dir}") except Exception: print(f"Exception occurred: {traceback.format_exc()}") finally: - if generated_files_path and config and not keep_deployment_files: - cleanup(generated_files_path=generated_files_path, config=config, is_android=True) + if config.generated_files_path and config and not keep_deployment_files: + cleanup(config=config, is_android=True) logging.info("[DEPLOY] End") @@ -193,6 +182,7 @@ if __name__ == "__main__": ) parser.add_argument("-c", "--config-file", type=lambda p: Path(p).absolute(), + default=(Path.cwd() / "pysidedeploy.spec"), help="Path to the .spec config file") parser.add_argument( diff --git a/sources/pyside-tools/deploy.py b/sources/pyside-tools/deploy.py index b86824a6..0aea807a 100644 --- a/sources/pyside-tools/deploy.py +++ b/sources/pyside-tools/deploy.py @@ -35,7 +35,7 @@ from pathlib import Path from textwrap import dedent from deploy_lib import (MAJOR_VERSION, Config, cleanup, config_option_exists, - finalize, get_config, install_python_dependencies, + finalize, create_config_file, install_python_dependencies, setup_python) @@ -54,29 +54,34 @@ def main(main_file: Path = None, name: str = None, config_file: Path = None, ini # Nuitka command to run command_str = None - generated_files_path = None config = None logging.info("[DEPLOY] Start") python = setup_python(dry_run=dry_run, force=force, init=init) - config = get_config(python_exe=python.exe, dry_run=dry_run, config_file=config_file, - main_file=main_file) + config_file_exists = config_file and Path(config_file).exists() + + if config_file_exists: + logging.info(f"[DEPLOY] Using existing config file {config_file}") + else: + config_file = create_config_file(dry_run=dry_run, config_file=config_file, + main_file=main_file) + + config = Config(config_file=config_file, source_file=main_file, python_exe=python.exe, + dry_run=dry_run, existing_config_file=config_file_exists) # set application name if name: config.title = name - source_file = config.project_dir / config.source_file - - generated_files_path = source_file.parent / "deployment" - cleanup(generated_files_path=generated_files_path, config=config) + cleanup(config=config) install_python_dependencies(config=config, python=python, init=init, packages="packages") # required by Nuitka for pyenv Python - if python.is_pyenv_python(): - config.extra_args += " --static-libpython=no" + add_arg = " --static-libpython=no" + if python.is_pyenv_python() and add_arg not in config.extra_args: + config.extra_args += add_arg # writing config file # in the case of --dry-run, we use default.spec as reference. Do not save the changes @@ -94,18 +99,16 @@ def main(main_file: Path = None, name: str = None, config_file: Path = None, ini if not dry_run: logging.info("[DEPLOY] Deploying application") - command_str = python.create_executable( - source_file=source_file, - extra_args=config.extra_args, - config=config, - ) + command_str = python.create_executable(source_file=config.source_file, + extra_args=config.extra_args, + config=config) except Exception: print(f"[DEPLOY] Exception occurred: {traceback.format_exc()}") finally: - if generated_files_path and config: - finalize(generated_files_path=generated_files_path, config=config) + if config.generated_files_path and config: + finalize(config=config) if not keep_deployment_files: - cleanup(generated_files_path=generated_files_path, config=Config) + cleanup(config=config) logging.info("[DEPLOY] End") return command_str diff --git a/sources/pyside-tools/deploy.pyproject b/sources/pyside-tools/deploy.pyproject index c0085fbe..eef1668c 100644 --- a/sources/pyside-tools/deploy.pyproject +++ b/sources/pyside-tools/deploy.pyproject @@ -1,6 +1,7 @@ { "files": ["deploy.py", "deploy_lib/__init__.py", "deploy_lib/commands.py", "deploy_lib/config.py", - "deploy_lib/default.spec", "deploy_lib/nuitka_helper.py", "deploy_lib/pyside_icon.jpg", + "deploy_lib/default.spec", "deploy_lib/nuitka_helper.py", "deploy_lib/pyside_icon.ico", + "deploy_lib/pyside_icon.icns","deploy_lib/pyside_icon.jpg", "deploy_lib/python_helper.py", "deploy_lib/deploy_util.py" ] } diff --git a/sources/pyside-tools/deploy_lib/__init__.py b/sources/pyside-tools/deploy_lib/__init__.py index 468a0367..8c5d8b4e 100644 --- a/sources/pyside-tools/deploy_lib/__init__.py +++ b/sources/pyside-tools/deploy_lib/__init__.py @@ -1,13 +1,25 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only import sys +from pathlib import Path MAJOR_VERSION = 6 -EXE_FORMAT = ".exe" if sys.platform == "win32" else ".bin" + +if sys.platform == "win32": + IMAGE_FORMAT = ".ico" + EXE_FORMAT = ".exe" +elif sys.platform == "darwin": + IMAGE_FORMAT = ".icns" + EXE_FORMAT = ".bin" +else: + IMAGE_FORMAT = ".jpg" + EXE_FORMAT = ".bin" + +DEFAULT_APP_ICON = str((Path(__file__).parent / f"pyside_icon{IMAGE_FORMAT}").resolve()) from .commands import run_command -from .config import BaseConfig, Config from .nuitka_helper import Nuitka -from .deploy_util import (cleanup, config_option_exists, finalize, get_config, - install_python_dependencies, setup_python) from .python_helper import PythonExecutable, find_pyside_modules +from .config import BaseConfig, Config +from .deploy_util import (cleanup, finalize, create_config_file, setup_python, + install_python_dependencies, config_option_exists) diff --git a/sources/pyside-tools/deploy_lib/android/__init__.py b/sources/pyside-tools/deploy_lib/android/__init__.py index 907e12e1..59cd510c 100644 --- a/sources/pyside-tools/deploy_lib/android/__init__.py +++ b/sources/pyside-tools/deploy_lib/android/__init__.py @@ -1,7 +1,7 @@ # Copyright (C) 2023 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only -from .android_helper import (AndroidData, create_recipe, extract_and_copy_jar, - find_lib_dependencies, find_qtlibs_in_wheel, - get_llvm_readobj, get_wheel_android_arch) -from .buildozer import Buildozer +from .android_helper import (create_recipe, extract_and_copy_jar, + get_wheel_android_arch, AndroidData, get_llvm_readobj, + find_lib_dependencies, find_qtlibs_in_wheel) +from .android_config import AndroidConfig diff --git a/sources/pyside-tools/deploy_lib/android/android_config.py b/sources/pyside-tools/deploy_lib/android/android_config.py new file mode 100644 index 00000000..442672a2 --- /dev/null +++ b/sources/pyside-tools/deploy_lib/android/android_config.py @@ -0,0 +1,256 @@ +# Copyright (C) 2023 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +import logging + +from typing import List +from pathlib import Path + +from . import extract_and_copy_jar, get_wheel_android_arch +from .. import Config, find_pyside_modules + +ANDROID_NDK_VERSION = "25c" +ANDROID_DEPLOY_CACHE = Path.home() / ".pyside6_android_deploy" + + +class AndroidConfig(Config): + """ + Wrapper class around pysidedeploy.spec file for pyside6-android-deploy + """ + def __init__(self, config_file: Path, source_file: Path, python_exe: Path, dry_run: bool, + android_data, existing_config_file: bool = False, + extra_ignore_dirs: List[str] = None): + super().__init__(config_file=config_file, source_file=source_file, python_exe=python_exe, + dry_run=dry_run, existing_config_file=existing_config_file) + + self.extra_ignore_dirs = extra_ignore_dirs + + if android_data.wheel_pyside: + self.wheel_pyside = android_data.wheel_pyside + else: + wheel_pyside_temp = self.get_value("android", "wheel_pyside") + if not wheel_pyside_temp: + raise RuntimeError("[DEPLOY] Unable to find PySide6 Android wheel") + self.wheel_pyside = Path(wheel_pyside_temp).resolve() + + if android_data.wheel_shiboken: + self.wheel_shiboken = android_data.wheel_shiboken + else: + wheel_shiboken_temp = self.get_value("android", "wheel_shiboken") + if not wheel_shiboken_temp: + raise RuntimeError("[DEPLOY] Unable to find shiboken6 Android wheel") + self.wheel_shiboken = Path(wheel_shiboken_temp).resolve() + + self.ndk_path = None + if android_data.ndk_path: + # from cli + self.ndk_path = android_data.ndk_path + else: + # from config + ndk_path_temp = self.get_value("buildozer", "ndk_path") + if ndk_path_temp: + self.ndk_path = Path(ndk_path_temp) + else: + ndk_path_temp = (ANDROID_DEPLOY_CACHE / "android-ndk" + / f"android-ndk-r{ANDROID_NDK_VERSION}") + if ndk_path_temp.exists(): + self.ndk_path = ndk_path_temp + + if self.ndk_path: + print(f"Using Android NDK: {str(self.ndk_path)}") + else: + raise FileNotFoundError("[DEPLOY] Unable to find Android NDK. Please pass the NDK " + "path either from the CLI or from pysidedeploy.spec") + + self.sdk_path = None + if android_data.sdk_path: + self.sdk_path = android_data.sdk_path + else: + sdk_path_temp = self.get_value("buildozer", "sdk_path") + if sdk_path_temp: + self.sdk_path = Path(sdk_path_temp) + else: + sdk_path_temp = ANDROID_DEPLOY_CACHE / "android-sdk" + if sdk_path_temp.exists(): + self.sdk_path = sdk_path_temp + else: + logging.info("[DEPLOY] Use default SDK from buildozer") + + if self.sdk_path: + print(f"Using Android SDK: {str(self.sdk_path)}") + + recipe_dir_temp = self.get_value("buildozer", "recipe_dir") + self.recipe_dir = Path(recipe_dir_temp) if recipe_dir_temp else None + + self._jars_dir = [] + jars_dir_temp = self.get_value("buildozer", "jars_dir") + if jars_dir_temp and Path(jars_dir_temp).resolve().exists(): + self.jars_dir = Path(jars_dir_temp).resolve() + + self._modules = [] + if self.get_value("buildozer", "modules"): + self.modules = self.get_value("buildozer", "modules").split(",") + else: + self._find_and_set_pysidemodules() + self._find_and_set_qtquick_modules() + + self._arch = None + if self.get_value("buildozer", "arch"): + self.arch = self.get_value("buildozer", "arch") + else: + self._find_and_set_arch() + + self._local_libs = [] + if self.get_value("buildozer", "local_libs"): + self.local_libs = self.get_value("buildozer", "local_libs").split(",") + + self._qt_plugins = [] + if self.get_value("android", "plugins"): + self._qt_plugins = self.get_value("android", "plugins").split(",") + + self._mode = self.get_value("buildozer", "mode") + + @property + def qt_plugins(self): + return self._qt_plugins + + @qt_plugins.setter + def qt_plugins(self, qt_plugins): + self._qt_plugins = qt_plugins + self.set_value("android", "plugins", ",".join(qt_plugins)) + + @property + def ndk_path(self): + return self._ndk_path + + @ndk_path.setter + def ndk_path(self, ndk_path: Path): + self._ndk_path = ndk_path.resolve() if ndk_path else None + if self._ndk_path: + self.set_value("buildozer", "ndk_path", str(self._ndk_path)) + + @property + def sdk_path(self) -> Path: + return self._sdk_path + + @sdk_path.setter + def sdk_path(self, sdk_path: Path): + self._sdk_path = sdk_path.resolve() if sdk_path else None + if self._sdk_path: + self.set_value("buildozer", "sdk_path", str(self._sdk_path)) + + @property + def arch(self): + return self._arch + + @arch.setter + def arch(self, arch): + self._arch = arch + self.set_value("buildozer", "arch", arch) + + @property + def mode(self): + return self._mode + + @property + def modules(self): + return self._modules + + @modules.setter + def modules(self, modules): + self._modules = modules + self.set_value("buildozer", "modules", ",".join(modules)) + + @property + def local_libs(self): + return self._local_libs + + @local_libs.setter + def local_libs(self, local_libs): + self._local_libs = local_libs + self.set_value("buildozer", "local_libs", ",".join(local_libs)) + + @property + def recipe_dir(self): + return self._recipe_dir + + @recipe_dir.setter + def recipe_dir(self, recipe_dir: Path): + self._recipe_dir = recipe_dir.resolve() if recipe_dir else None + if self._recipe_dir: + self.set_value("buildozer", "recipe_dir", str(self._recipe_dir)) + + def recipes_exist(self): + if not self._recipe_dir: + return False + + pyside_recipe_dir = Path(self.recipe_dir) / "PySide6" + shiboken_recipe_dir = Path(self.recipe_dir) / "shiboken6" + + return pyside_recipe_dir.is_dir() and shiboken_recipe_dir.is_dir() + + @property + def jars_dir(self) -> Path: + return self._jars_dir + + @jars_dir.setter + def jars_dir(self, jars_dir: Path): + self._jars_dir = jars_dir.resolve() if jars_dir else None + if self._jars_dir: + self.set_value("buildozer", "jars_dir", str(self._jars_dir)) + + @property + def wheel_pyside(self) -> Path: + return self._wheel_pyside + + @wheel_pyside.setter + def wheel_pyside(self, wheel_pyside: Path): + self._wheel_pyside = wheel_pyside.resolve() if wheel_pyside else None + if self._wheel_pyside: + self.set_value("android", "wheel_pyside", str(self._wheel_pyside)) + + @property + def wheel_shiboken(self) -> Path: + return self._wheel_shiboken + + @wheel_shiboken.setter + def wheel_shiboken(self, wheel_shiboken: Path): + self._wheel_shiboken = wheel_shiboken.resolve() if wheel_shiboken else None + if self._wheel_shiboken: + self.set_value("android", "wheel_shiboken", str(self._wheel_shiboken)) + + def _find_and_set_pysidemodules(self): + self.modules = find_pyside_modules(project_dir=self.project_dir, + extra_ignore_dirs=self.extra_ignore_dirs, + project_data=self.project_data) + logging.info("The following PySide modules were found from the python files of " + f"the project {self.modules}") + + def find_and_set_jars_dir(self): + """Extract out and copy .jar files to {generated_files_path} + """ + if not self.dry_run: + logging.info("[DEPLOY] Extract and copy jar files from PySide6 wheel to " + f"{self.generated_files_path}") + self.jars_dir = extract_and_copy_jar(wheel_path=self.wheel_pyside, + generated_files_path=self.generated_files_path) + + def _find_and_set_arch(self): + """Find architecture from wheel name + """ + self.arch = get_wheel_android_arch(wheel=self.wheel_pyside) + if not self.arch: + raise RuntimeError("[DEPLOY] PySide wheel corrupted. Wheel name should end with" + "platform name") + + def _find_and_set_qtquick_modules(self): + """Identify if QtQuick is used in QML files and add them as dependency + """ + extra_modules = [] + + if "QtQuick" in self.qml_modules: + extra_modules.append("Quick") + + if "QtQuick.Controls" in self.qml_modules: + extra_modules.append("QuickControls2") + + self.modules += extra_modules diff --git a/sources/pyside-tools/deploy_lib/android/android_helper.py b/sources/pyside-tools/deploy_lib/android/android_helper.py index 2299eab2..23034364 100644 --- a/sources/pyside-tools/deploy_lib/android/android_helper.py +++ b/sources/pyside-tools/deploy_lib/android/android_helper.py @@ -39,7 +39,7 @@ def create_recipe(version: str, component: str, wheel_path: str, generated_files qt_local_libs = [] if local_libs: - qt_local_libs = [local_lib for local_lib in local_libs if local_lib.startswith("Qt6") ] + qt_local_libs = [local_lib for local_lib in local_libs if local_lib.startswith("Qt6")] rcp_tmpl_path = Path(__file__).parent / "recipes" / f"{component}" environment = Environment(loader=FileSystemLoader(rcp_tmpl_path)) diff --git a/sources/pyside-tools/deploy_lib/android/buildozer.py b/sources/pyside-tools/deploy_lib/android/buildozer.py index f66a4b01..3c188b1c 100644 --- a/sources/pyside-tools/deploy_lib/android/buildozer.py +++ b/sources/pyside-tools/deploy_lib/android/buildozer.py @@ -1,6 +1,7 @@ # Copyright (C) 2023 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +import sys import logging import re import tempfile @@ -22,8 +23,7 @@ ALL_PYSIDE_MODULES = [module[2:] for module in PySide6.__all__] class BuildozerConfig(BaseConfig): - def __init__(self, buildozer_spec_file: Path, pysidedeploy_config: Config, - generated_files_path: Path): + def __init__(self, buildozer_spec_file: Path, pysidedeploy_config: Config): super().__init__(buildozer_spec_file, comment_prefixes="#") self.set_value("app", "title", pysidedeploy_config.title) self.set_value("app", "package.name", pysidedeploy_config.title) @@ -51,10 +51,6 @@ class BuildozerConfig(BaseConfig): self.set_value("app", "android.archs", self.arch) # p4a changes - logging.info("[DEPLOY] Using custom fork of python-for-android: " - "https://github.com/shyamnathp/python-for-android/tree/pyside_support") - self.set_value("app", "p4a.fork", "shyamnathp") - self.set_value("app", "p4a.branch", "pyside_support_2") self.set_value("app", "p4a.bootstrap", "qt") self.qt_libs_path: zipfile.Path = ( @@ -64,11 +60,6 @@ class BuildozerConfig(BaseConfig): extra_modules = self.__find_dependent_qt_modules(pysidedeploy_config) logging.info(f"[DEPLOY] Other dependent modules to be added: {extra_modules}") pysidedeploy_config.modules = pysidedeploy_config.modules + extra_modules - - # update the config file with the extra modules - if extra_modules: - pysidedeploy_config.update_config() - modules = ",".join(pysidedeploy_config.modules) # gets the xml dependency files from Qt installation path @@ -83,9 +74,6 @@ class BuildozerConfig(BaseConfig): self.__find_plugin_dependencies(dependency_files, dependent_plugins) pysidedeploy_config.qt_plugins += dependent_plugins - if local_libs or dependent_plugins: - pysidedeploy_config.update_config() - local_libs = ",".join(pysidedeploy_config.local_libs) # create recipes @@ -97,14 +85,15 @@ class BuildozerConfig(BaseConfig): version = Wheel(pysidedeploy_config.wheel_pyside).version create_recipe(version=version, component=f"PySide{MAJOR_VERSION}", wheel_path=pysidedeploy_config.wheel_pyside, - generated_files_path=generated_files_path, + generated_files_path=pysidedeploy_config.generated_files_path, qt_modules=pysidedeploy_config.modules, local_libs=pysidedeploy_config.local_libs, plugins=pysidedeploy_config.qt_plugins) create_recipe(version=version, component=f"shiboken{MAJOR_VERSION}", wheel_path=pysidedeploy_config.wheel_shiboken, - generated_files_path=generated_files_path) - pysidedeploy_config.recipe_dir = (generated_files_path / "recipes").resolve() + generated_files_path=pysidedeploy_config.generated_files_path) + pysidedeploy_config.recipe_dir = ((pysidedeploy_config.generated_files_path + / "recipes").resolve()) self.set_value('app', "p4a.local_recipes", str(pysidedeploy_config.recipe_dir)) # add permissions @@ -129,6 +118,9 @@ class BuildozerConfig(BaseConfig): # change final apk/aab path self.set_value("buildozer", "bin_dir", str(pysidedeploy_config.exe_dir.resolve())) + # set application icon + self.set_value("app", "icon.filename", pysidedeploy_config.icon) + self.update_config() def __get_dependency_files(self, modules: List[str], arch: str) -> List[zipfile.Path]: @@ -261,8 +253,8 @@ class BuildozerConfig(BaseConfig): else: continue - absolute_plugin_module_folder = (self.qt_libs_path.parent / "plugins" / - plugin_module_folder) + absolute_plugin_module_folder = (self.qt_libs_path.parent / "plugins" + / plugin_module_folder) if not absolute_plugin_module_folder.is_dir(): logging.warning(f"[DEPLOY] Qt plugin folder '{plugin_module_folder}' does not" @@ -332,7 +324,7 @@ class Buildozer: dry_run = False @staticmethod - def initialize(pysidedeploy_config: Config, generated_files_path: Path): + def initialize(pysidedeploy_config: Config): project_dir = Path(pysidedeploy_config.project_dir) buildozer_spec = project_dir / "buildozer.spec" if buildozer_spec.exists(): @@ -341,14 +333,14 @@ class Buildozer: return # creates buildozer.spec config file - command = ["buildozer", "init"] + command = [sys.executable, "-m", "buildozer", "init"] run_command(command=command, dry_run=Buildozer.dry_run) if not Buildozer.dry_run: if not buildozer_spec.exists(): raise RuntimeError(f"buildozer.spec not found in {Path.cwd()}") - BuildozerConfig(buildozer_spec, pysidedeploy_config, generated_files_path) + BuildozerConfig(buildozer_spec, pysidedeploy_config) @staticmethod def create_executable(mode: str): - command = ["buildozer", "android", mode] + command = [sys.executable, "-m", "buildozer", "android", mode] run_command(command=command, dry_run=Buildozer.dry_run) diff --git a/sources/pyside-tools/deploy_lib/android/recipes/PySide6/__init__.tmpl.py b/sources/pyside-tools/deploy_lib/android/recipes/PySide6/__init__.tmpl.py index 37669cff..8a861579 100644 --- a/sources/pyside-tools/deploy_lib/android/recipes/PySide6/__init__.tmpl.py +++ b/sources/pyside-tools/deploy_lib/android/recipes/PySide6/__init__.tmpl.py @@ -35,30 +35,30 @@ class PySideRecipe(PythonRecipe): shutil.copyfile(lib_dir.parent.parent / "libpyside6.abi3.so", Path(self.ctx.get_libs_dir(arch.arch)) / "libpyside6.abi3.so") - {% for module in qt_modules %} + {% for module in qt_modules %} # noqa: E999 shutil.copyfile(lib_dir.parent.parent / f"Qt{{ module }}.abi3.so", Path(self.ctx.get_libs_dir(arch.arch)) / "Qt{{ module }}.abi3.so") - {% if module == "Qml" -%} + {% if module == "Qml" -%} # noqa: E999 shutil.copyfile(lib_dir.parent.parent / "libpyside6qml.abi3.so", Path(self.ctx.get_libs_dir(arch.arch)) / "libpyside6qml.abi3.so") - {% endif %} - {% endfor %} + {% endif %} # noqa: E999 + {% endfor %} # noqa: E999 - {% for lib in qt_local_libs %} + {% for lib in qt_local_libs %} # noqa: E999 lib_path = lib_dir / f"lib{{ lib }}_{arch.arch}.so" if lib_path.exists(): shutil.copyfile(lib_path, Path(self.ctx.get_libs_dir(arch.arch)) / f"lib{{ lib }}_{arch.arch}.so") - {% endfor %} + {% endfor %} # noqa: E999 - {% for plugin_category,plugin_name in qt_plugins %} + {% for plugin_category,plugin_name in qt_plugins %} # noqa: E999 plugin_path = (lib_dir.parent / "plugins" / "{{ plugin_category }}" / f"libplugins_{{ plugin_category }}_{{ plugin_name }}_{arch.arch}.so") if plugin_path.exists(): shutil.copyfile(plugin_path, (Path(self.ctx.get_libs_dir(arch.arch)) / f"libplugins_{{ plugin_category }}_{{ plugin_name }}_{arch.arch}.so")) - {% endfor %} + {% endfor %} # noqa: E999 recipe = PySideRecipe() diff --git a/sources/pyside-tools/deploy_lib/config.py b/sources/pyside-tools/deploy_lib/config.py index f6898ad7..5dbaa68e 100644 --- a/sources/pyside-tools/deploy_lib/config.py +++ b/sources/pyside-tools/deploy_lib/config.py @@ -10,13 +10,11 @@ from pathlib import Path from project import ProjectData from .commands import run_qmlimportscanner +from . import DEFAULT_APP_ICON # Some QML plugins like QtCore are excluded from this list as they don't contribute much to # executable size. Excluding them saves the extra processing of checking for them in files EXCLUDED_QML_PLUGINS = {"QtQuick", "QtQuick3D", "QtCharts", "QtWebEngine", "QtTest", "QtSensors"} -# TODO: Move this to android module. Fix circular import. -ANDROID_NDK_VERSION = "25c" -ANDROID_DEPLOY_CACHE = Path.home() / ".pyside6_android_deploy" class BaseConfig: @@ -63,14 +61,15 @@ class Config(BaseConfig): """ def __init__(self, config_file: Path, source_file: Path, python_exe: Path, dry_run: bool, - android_data, is_android: bool, existing_config_file: bool = False): + existing_config_file: bool = False): super().__init__(config_file=config_file, existing_config_file=existing_config_file) self._dry_run = dry_run + self.qml_modules = set() # set source_file self.source_file = Path( self.set_or_fetch(config_property_val=source_file, config_property_key="input_file") - ) + ).resolve() # set python path self.python_path = Path( @@ -83,6 +82,13 @@ class Config(BaseConfig): self.title = self.get_value("app", "title") + # set application icon + config_icon = self.get_value("app", "icon") + if config_icon: + self.icon = str(Path(config_icon).resolve()) + else: + self.icon = DEFAULT_APP_ICON + self.project_dir = None if self.get_value("app", "project_dir"): self.project_dir = Path(self.get_value("app", "project_dir")).absolute() @@ -115,83 +121,7 @@ class Config(BaseConfig): else: self._find_and_set_excluded_qml_plugins() - # Android - if is_android: - if android_data.wheel_pyside: - self.wheel_pyside = android_data.wheel_pyside - else: - wheel_pyside_temp = self.get_value("qt", "wheel_pyside") - if not wheel_pyside_temp: - raise RuntimeError("[DEPLOY] Unable to find PySide6 Android wheel") - self.wheel_pyside = Path(wheel_pyside_temp).resolve() - - if android_data.wheel_shiboken: - self.wheel_shiboken = android_data.wheel_shiboken - else: - wheel_shiboken_temp = self.get_value("qt", "wheel_shiboken") - if not wheel_shiboken_temp: - raise RuntimeError("[DEPLOY] Unable to find shiboken6 Android wheel") - self.wheel_shiboken = Path(wheel_shiboken_temp).resolve() - - self.ndk_path = None - if android_data.ndk_path: - # from cli - self.ndk_path = android_data.ndk_path - else: - # from config - ndk_path_temp = self.get_value("buildozer", "ndk_path") - if ndk_path_temp: - self.ndk_path = Path(ndk_path_temp) - else: - ndk_path_temp = (ANDROID_DEPLOY_CACHE / "android-ndk" - / f"android-ndk-r{ANDROID_NDK_VERSION}") - if ndk_path_temp.exists(): - self.ndk_path = ndk_path_temp - - if self.ndk_path: - print(f"Using Android NDK: {str(self.ndk_path)}") - else: - raise FileNotFoundError("[DEPLOY] Unable to find Android NDK. Please pass the NDK " - "path either from the CLI or from pysidedeploy.spec") - - self.sdk_path = None - if android_data.sdk_path: - self.sdk_path = android_data.sdk_path - else: - sdk_path_temp = self.get_value("buildozer", "sdk_path") - if sdk_path_temp: - self.sdk_path = Path(sdk_path_temp) - else: - sdk_path_temp = ANDROID_DEPLOY_CACHE / "android-sdk" - if sdk_path_temp.exists(): - self.sdk_path = sdk_path_temp - else: - logging.info("[DEPLOY] Use default SDK from buildozer") - - if self.sdk_path: - print(f"Using Android SDK: {str(self.sdk_path)}") - - recipe_dir_temp = self.get_value("buildozer", "recipe_dir") - self.recipe_dir = Path(recipe_dir_temp) if recipe_dir_temp else None - - jars_dir_temp = self.get_value("buildozer", "jars_dir") - self.jars_dir = Path(jars_dir_temp) if jars_dir_temp else None - - self._modules = [] - if self.get_value("buildozer", "modules"): - self.modules = self.get_value("buildozer", "modules").split(",") - - self.arch = self.get_value("buildozer", "arch") - - self._local_libs = [] - if self.get_value("buildozer", "local_libs"): - self.local_libs = self.get_value("buildozer", "local_libs").split(",") - - self._qt_plugins = [] - if self.get_value("qt", "plugins"): - self._qt_plugins = self.get_value("qt", "plugins").split(",") - - self._mode = self.get_value("buildozer", "mode") + self._generated_files_path = self.project_dir / "deployment" def set_or_fetch(self, config_property_val, config_property_key, config_property_group="app"): """ @@ -214,6 +144,10 @@ class Config(BaseConfig): def dry_run(self): return self._dry_run + @property + def generated_files_path(self): + return self._generated_files_path + @property def qml_files(self): return self._qml_files @@ -239,6 +173,15 @@ class Config(BaseConfig): self._title = title self.set_value("app", "title", title) + @property + def icon(self): + return self._icon + + @icon.setter + def icon(self, icon): + self._icon = icon + self.set_value("app", "icon", icon) + @property def source_file(self): return self._source_file @@ -271,95 +214,6 @@ class Config(BaseConfig): def excluded_qml_plugins(self, excluded_qml_plugins): self._excluded_qml_plugins = excluded_qml_plugins - @property - def recipe_dir(self): - return self._recipe_dir - - @recipe_dir.setter - def recipe_dir(self, recipe_dir: Path): - self._recipe_dir = recipe_dir.resolve() if recipe_dir else None - if self._recipe_dir: - self.set_value("buildozer", "recipe_dir", str(self._recipe_dir)) - - def recipes_exist(self): - if not self._recipe_dir: - return False - - pyside_recipe_dir = Path(self.recipe_dir) / "PySide6" - shiboken_recipe_dir = Path(self.recipe_dir) / "shiboken6" - - return pyside_recipe_dir.is_dir() and shiboken_recipe_dir.is_dir() - - @property - def jars_dir(self) -> Path: - return self._jars_dir - - @jars_dir.setter - def jars_dir(self, jars_dir: Path): - self._jars_dir = jars_dir.resolve() if jars_dir else None - if self._jars_dir: - self.set_value("buildozer", "jars_dir", str(self._jars_dir)) - - @property - def modules(self): - return self._modules - - @modules.setter - def modules(self, modules): - self._modules = modules - self.set_value("buildozer", "modules", ",".join(modules)) - - @property - def local_libs(self): - return self._local_libs - - @local_libs.setter - def local_libs(self, local_libs): - self._local_libs = local_libs - self.set_value("buildozer", "local_libs", ",".join(local_libs)) - - @property - def qt_plugins(self): - return self._qt_plugins - - @qt_plugins.setter - def qt_plugins(self, qt_plugins): - self._qt_plugins = qt_plugins - self.set_value("qt", "plugins", ",".join(qt_plugins)) - - @property - def ndk_path(self): - return self._ndk_path - - @ndk_path.setter - def ndk_path(self, ndk_path: Path): - self._ndk_path = ndk_path.resolve() if ndk_path else None - if self._ndk_path: - self.set_value("buildozer", "ndk_path", str(self._ndk_path)) - - @property - def sdk_path(self) -> Path: - return self._sdk_path - - @sdk_path.setter - def sdk_path(self, sdk_path: Path): - self._sdk_path = sdk_path.resolve() if sdk_path else None - if self._sdk_path: - self.set_value("buildozer", "sdk_path", str(self._sdk_path)) - - @property - def arch(self): - return self._arch - - @arch.setter - def arch(self, arch): - self._arch = arch - self.set_value("buildozer", "arch", arch) - - @property - def mode(self): - return self._mode - @property def exe_dir(self): return self._exe_dir @@ -368,26 +222,6 @@ class Config(BaseConfig): def exe_dir(self, exe_dir: Path): self._exe_dir = exe_dir - @property - def wheel_pyside(self) -> Path: - return self._wheel_pyside - - @wheel_pyside.setter - def wheel_pyside(self, wheel_pyside: Path): - self._wheel_pyside = wheel_pyside.resolve() if wheel_pyside else None - if self._wheel_pyside: - self.set_value("qt", "wheel_pyside", str(self._wheel_pyside)) - - @property - def wheel_shiboken(self) -> Path: - return self._wheel_shiboken - - @wheel_shiboken.setter - def wheel_shiboken(self, wheel_shiboken: Path): - self._wheel_shiboken = wheel_shiboken.resolve() if wheel_shiboken else None - if self._wheel_shiboken: - self.set_value("qt", "wheel_shiboken", str(self._wheel_shiboken)) - def _find_and_set_qml_files(self): """Fetches all the qml_files in the folder and sets them if the field qml_files is empty in the config_dir""" @@ -478,9 +312,9 @@ class Config(BaseConfig): def _find_and_set_excluded_qml_plugins(self): if self.qml_files: - included_qml_modules = set(run_qmlimportscanner(qml_files=self.qml_files, - dry_run=self.dry_run)) - self.excluded_qml_plugins = EXCLUDED_QML_PLUGINS.difference(included_qml_modules) + self.qml_modules = set(run_qmlimportscanner(qml_files=self.qml_files, + dry_run=self.dry_run)) + self.excluded_qml_plugins = EXCLUDED_QML_PLUGINS.difference(self.qml_modules) # needed for dry_run testing self.excluded_qml_plugins = sorted(self.excluded_qml_plugins) diff --git a/sources/pyside-tools/deploy_lib/default.spec b/sources/pyside-tools/deploy_lib/default.spec index c49ef53b..7ca2edfe 100644 --- a/sources/pyside-tools/deploy_lib/default.spec +++ b/sources/pyside-tools/deploy_lib/default.spec @@ -16,6 +16,9 @@ exec_directory = # Path to .pyproject project file project_file = +# Application icon +icon = + [python] # Python path @@ -24,7 +27,7 @@ python_path = # python packages to install # ordered-set: increase compile time performance of nuitka packaging # zstandard: provides final executable size optimization -packages = nuitka==1.5.4,ordered_set,zstandard +packages = nuitka==1.8.0,ordered_set,zstandard # buildozer: for deploying Android application android_packages = buildozer==1.5.0,cython==0.29.33 @@ -32,12 +35,14 @@ android_packages = buildozer==1.5.0,cython==0.29.33 [qt] # Comma separated path to QML files required -# normally all the QML files are added automatically +# normally all the QML files required by the project are added automatically qml_files = # excluded qml plugin binaries excluded_qml_plugins = +[android] + # path to PySide wheel wheel_pyside = @@ -51,7 +56,7 @@ plugins = platforms_qtforandroid # (str) specify any extra nuitka arguments # eg: extra_args = --show-modules --follow-stdlib -extra_args = --quiet --noinclude-qt-translations=True +extra_args = --quiet --noinclude-qt-translations [buildozer] diff --git a/sources/pyside-tools/deploy_lib/deploy_util.py b/sources/pyside-tools/deploy_lib/deploy_util.py index a73ccb0e..a8ca5861 100644 --- a/sources/pyside-tools/deploy_lib/deploy_util.py +++ b/sources/pyside-tools/deploy_lib/deploy_util.py @@ -19,12 +19,12 @@ def config_option_exists(): return False -def cleanup(generated_files_path: Path, config: Config, is_android: bool = False): +def cleanup(config: Config, is_android: bool = False): """ Cleanup the generated build folders/files """ - if generated_files_path.exists(): - shutil.rmtree(generated_files_path) + if config.generated_files_path.exists(): + shutil.rmtree(config.generated_files_path) logging.info("[DEPLOY] Deployment directory purged") if is_android: @@ -39,37 +39,27 @@ def cleanup(generated_files_path: Path, config: Config, is_android: bool = False logging.info(f"[DEPLOY] {str(buildozer_build)} removed") -def get_config(python_exe: Path, dry_run: bool = False, config_file: Path = None, main_file: - Path = None, android_data=None, is_android: bool = False): +def create_config_file(dry_run: bool = False, config_file: Path = None, main_file: Path = None): """ Sets up a new pysidedeploy.spec or use an existing config file """ - config_file_exists = config_file and Path(config_file).exists() - - if main_file and not config_file_exists: + if main_file: if main_file.parent != Path.cwd(): config_file = main_file.parent / "pysidedeploy.spec" else: config_file = Path.cwd() / "pysidedeploy.spec" - if config_file_exists: - logging.info(f"[DEPLOY] Using existing config file {config_file}") - else: - logging.info(f"[DEPLOY] Creating config file {config_file}") - if not dry_run: - shutil.copy(Path(__file__).parent / "default.spec", config_file) + logging.info(f"[DEPLOY] Creating config file {config_file}") + if not dry_run: + shutil.copy(Path(__file__).parent / "default.spec", config_file) # the config parser needs a reference to parse. So, in the case of --dry-run # use the default.spec file. - if dry_run and not config_file_exists: + if dry_run: config_file = Path(__file__).parent / "default.spec" - config = Config(config_file=config_file, source_file=main_file, python_exe=python_exe, - dry_run=dry_run, android_data=android_data, is_android=is_android, - existing_config_file=config_file_exists) - - return config + return config_file def setup_python(dry_run: bool, force: bool, init: bool): @@ -99,22 +89,28 @@ def install_python_dependencies(config: Config, python: PythonExecutable, init: """ Installs the python package dependencies for the target deployment platform """ + packages = config.get_value("python", packages).split(",") if not init: # install packages needed for deployment logging.info("[DEPLOY] Installing dependencies") - packages = config.get_value("python", packages).split(",") python.install(packages=packages) # nuitka requires patchelf to make patchelf rpath changes for some Qt files if sys.platform.startswith("linux") and not is_android: python.install(packages=["patchelf"]) + elif is_android: + # install only buildozer + logging.info("[DEPLOY] Installing buildozer") + buildozer_package_with_version = ([package for package in packages + if package.startswith("buildozer")]) + python.install(packages=list(buildozer_package_with_version)) -def finalize(generated_files_path: Path, config: Config): +def finalize(config: Config): """ Copy the executable into the final location For Android deployment, this is done through buildozer """ - generated_exec_path = generated_files_path / (config.source_file.stem + EXE_FORMAT) + generated_exec_path = config.generated_files_path / (config.source_file.stem + EXE_FORMAT) if generated_exec_path.exists() and config.exe_dir: shutil.copy(generated_exec_path, config.exe_dir) print("[DEPLOY] Executed file created in " diff --git a/sources/pyside-tools/deploy_lib/nuitka_helper.py b/sources/pyside-tools/deploy_lib/nuitka_helper.py index ae3d2ff9..ae5834b6 100644 --- a/sources/pyside-tools/deploy_lib/nuitka_helper.py +++ b/sources/pyside-tools/deploy_lib/nuitka_helper.py @@ -18,8 +18,17 @@ class Nuitka: def __init__(self, nuitka): self.nuitka = nuitka + @staticmethod + def icon_option(): + if sys.platform == "linux": + return "--linux-icon" + elif sys.platform == "win32": + return "--windows-icon-from-ico" + else: + return "--macos-app-icon" + def create_executable(self, source_file: Path, extra_args: str, qml_files: List[Path], - excluded_qml_plugins, dry_run: bool): + excluded_qml_plugins: List[str], icon: str, dry_run: bool): extra_args = extra_args.split() qml_args = [] if qml_files: @@ -51,10 +60,7 @@ class Nuitka: f"--output-dir={output_dir}", ] command.extend(extra_args + qml_args) - - if sys.platform == "linux": - linux_icon = str(Path(__file__).parent / "pyside_icon.jpg") - command.append(f"--linux-onefile-icon={linux_icon}") + command.append(f"{self.__class__.icon_option()}={icon}") command_str, _ = run_command(command=command, dry_run=dry_run) return command_str diff --git a/sources/pyside-tools/deploy_lib/pyside_icon.icns b/sources/pyside-tools/deploy_lib/pyside_icon.icns new file mode 100644 index 00000000..a6eb02bb Binary files /dev/null and b/sources/pyside-tools/deploy_lib/pyside_icon.icns differ diff --git a/sources/pyside-tools/deploy_lib/pyside_icon.ico b/sources/pyside-tools/deploy_lib/pyside_icon.ico new file mode 100644 index 00000000..332a3a56 Binary files /dev/null and b/sources/pyside-tools/deploy_lib/pyside_icon.ico differ diff --git a/sources/pyside-tools/deploy_lib/python_helper.py b/sources/pyside-tools/deploy_lib/python_helper.py index f403b2d4..6ec3b64f 100644 --- a/sources/pyside-tools/deploy_lib/python_helper.py +++ b/sources/pyside-tools/deploy_lib/python_helper.py @@ -12,7 +12,7 @@ from importlib import util from importlib.metadata import version from pathlib import Path -from . import Config, Nuitka, run_command +from . import Nuitka, run_command IMPORT_WARNING_PYSIDE = (f"[DEPLOY] Found 'import PySide6' in file {0}" ". Use 'from PySide6 import ' or pass the module" @@ -155,8 +155,8 @@ class PythonExecutable: return False def install(self, packages: list = None): - _, installed_packages = run_command(command=[str(self.exe), "-m", "pip", "freeze"], dry_run=False - , fetch_output=True) + _, installed_packages = run_command(command=[str(self.exe), "-m", "pip", "freeze"], + dry_run=False, fetch_output=True) installed_packages = [p.decode().split('==')[0] for p in installed_packages.split()] for package in packages: package_info = package.split('==') @@ -193,16 +193,16 @@ class PythonExecutable: def is_installed(self, package): return bool(util.find_spec(package)) - def create_executable(self, source_file: Path, extra_args: str, config: Config): + def create_executable(self, source_file: Path, extra_args: str, config): if config.qml_files: logging.info(f"[DEPLOY] Included QML files: {config.qml_files}") - command_str = self.nuitka.create_executable( - source_file=source_file, - extra_args=extra_args, - qml_files=config.qml_files, - excluded_qml_plugins=config.excluded_qml_plugins, - dry_run=self.dry_run, - ) + command_str = self.nuitka.create_executable(source_file=source_file, + extra_args=extra_args, + qml_files=config.qml_files, + excluded_qml_plugins=(config. + excluded_qml_plugins), + icon=config.icon, + dry_run=self.dry_run) return command_str diff --git a/sources/pyside-tools/project.py b/sources/pyside-tools/project.py index 3dbe4254..621bf1ac 100644 --- a/sources/pyside-tools/project.py +++ b/sources/pyside-tools/project.py @@ -51,6 +51,7 @@ NEW_PROJECT_TYPES = {"new-quick": ProjectType.QUICK, "new-ui": ProjectType.WIDGET_FORM, "new-widget": ProjectType.WIDGET} + class Project: """ Class to wrap the various operations on Project @@ -117,7 +118,7 @@ class Project: qmltypes_file = self._qml_module_dir / f"{stem}.qmltypes" cpp_file = self._qml_module_dir / f"{stem}_qmltyperegistrations.cpp" cmd = [QMLTYPEREGISTRAR_CMD, "--generate-qmltypes", - os.fspath(qmltypes_file), "-o", os.fspath(cpp_file), + os.fspath(qmltypes_file), "-o", os.fspath(cpp_file), os.fspath(file)] cmd.extend(self._qml_project_data.registrar_options()) return ([qmltypes_file, cpp_file], cmd) diff --git a/sources/pyside-tools/project/newproject.py b/sources/pyside-tools/project/newproject.py index 5211c397..c363a9fc 100644 --- a/sources/pyside-tools/project/newproject.py +++ b/sources/pyside-tools/project/newproject.py @@ -145,7 +145,7 @@ def _qml_project() -> Project: def new_project(directory_s: str, - project_type: ProjectType=ProjectType.WIDGET_FORM) -> int: + project_type: ProjectType = ProjectType.WIDGET_FORM) -> int: directory = Path(directory_s) if directory.exists(): print(f"{directory_s} already exists.", file=sys.stderr) diff --git a/sources/pyside-tools/project/project_data.py b/sources/pyside-tools/project/project_data.py index b8d27f33..416089dc 100644 --- a/sources/pyside-tools/project/project_data.py +++ b/sources/pyside-tools/project/project_data.py @@ -11,6 +11,7 @@ from . import (METATYPES_JSON_SUFFIX, PROJECT_FILE_SUFFIX, qt_metatype_json_dir, MOD_CMD, QML_IMPORT_MAJOR_VERSION, QML_IMPORT_MINOR_VERSION, QML_IMPORT_NAME, QT_MODULES) + def is_python_file(file: Path) -> bool: return (file.suffix == ".py" or sys.platform == "win32" and file.suffix == ".pyw") @@ -106,7 +107,7 @@ class ProjectData: # __main__ not found print( - "Python file with main function not found. Add the file to" f" {project_file}", + "Python file with main function not found. Add the file to" f" {self.project_file}", file=sys.stderr, ) sys.exit(1) @@ -233,4 +234,3 @@ def check_qml_decorators(py_file: Path) -> Tuple[bool, QmlProjectData]: if v: qml_project_data.qt_modules = v return (has_class, qml_project_data) - diff --git a/sources/pyside-tools/pyside_tool.py b/sources/pyside-tools/pyside_tool.py index 1648b294..7daacc22 100644 --- a/sources/pyside-tools/pyside_tool.py +++ b/sources/pyside-tools/pyside_tool.py @@ -8,13 +8,23 @@ import subprocess import sys import sysconfig from pathlib import Path -from subprocess import PIPE, Popen import PySide6 as ref_mod VIRTUAL_ENV = "VIRTUAL_ENV" +def is_pyenv_python(): + pyenv_root = os.environ.get("PYENV_ROOT") + + if pyenv_root: + resolved_exe = Path(sys.executable).resolve() + if str(resolved_exe).startswith(pyenv_root): + return True + + return False + + def is_virtual_env(): return sys.prefix != sys.base_prefix @@ -46,13 +56,11 @@ def qt_tool_wrapper(qt_tool, args, libexec=False): exe = pyside_dir / qt_tool cmd = [os.fspath(exe)] + args - proc = Popen(cmd, stderr=PIPE) - out, err = proc.communicate() - if err: - msg = err.decode("utf-8") + returncode = subprocess.call(cmd) + if returncode != 0: command = ' '.join(cmd) - print(f"Error: {msg}\nwhile executing '{command}'") - sys.exit(proc.returncode) + print(f"'{command}' returned {returncode}", file=sys.stderr) + sys.exit(returncode) def pyside_script_wrapper(script_name): @@ -147,11 +155,23 @@ def designer(): # Determine library name (examples/utils/pyside_config.py) version = f'{major_version}.{minor_version}' library_name = f'libpython{version}{sys.abiflags}.so' + if is_pyenv_python(): + library_name = str(Path(sysconfig.get_config_var('LIBDIR')) / library_name) os.environ['LD_PRELOAD'] = library_name elif sys.platform == 'darwin': library_name = sysconfig.get_config_var("LDLIBRARY") framework_prefix = sysconfig.get_config_var("PYTHONFRAMEWORKPREFIX") - lib_path = os.fspath(Path(framework_prefix) / library_name) + lib_path = None + if framework_prefix: + lib_path = os.fspath(Path(framework_prefix) / library_name) + elif is_pyenv_python(): + lib_path = str(Path(sysconfig.get_config_var('LIBDIR')) / library_name) + else: + # ideally this should never be reached because the system Python and Python installed + # from python.org are all framework builds + print("Unable to find Python library directory. Use a framework build of Python.", + file=sys.stderr) + sys.exit(0) os.environ['DYLD_INSERT_LIBRARIES'] = lib_path elif sys.platform == 'win32': # Find Python DLLs from the base installation @@ -197,11 +217,13 @@ def android_deploy(): if not sys.platform == "linux": print("pyside6-android-deploy only works from a Linux host") else: - dependent_packages = ["jinja2", "pkginfo"] - for dependent_package in dependent_packages: - if not bool(importlib.util.find_spec(dependent_package)): - command = [sys.executable, "-m", "pip", "install", dependent_package] - subprocess.run(command) + android_requirements_file = Path(__file__).parent / "requirements-android.txt" + with open(android_requirements_file, 'r', encoding='UTF-8') as file: + while line := file.readline(): + dependent_package = line.rstrip() + if not bool(importlib.util.find_spec(dependent_package)): + command = [sys.executable, "-m", "pip", "install", dependent_package] + subprocess.run(command) pyside_script_wrapper("android_deploy.py") diff --git a/sources/pyside-tools/qml.py b/sources/pyside-tools/qml.py index 61e0e8ff..d76e5c5c 100644 --- a/sources/pyside-tools/qml.py +++ b/sources/pyside-tools/qml.py @@ -215,7 +215,7 @@ if __name__ == "__main__": if apptype == "core": component = QQmlComponent(engine, qml_file) obj = component.create() - filtered_attributes = {k: v for k, v in vars(obj).items() if type(v) != SignalInstance} + filtered_attributes = {k: v for k, v in vars(obj).items() if type(v) is not SignalInstance} logging.info("qml: component object attributes are") pprint(filtered_attributes) del engine @@ -227,7 +227,7 @@ if __name__ == "__main__": sys.exit(-1) qquick_view = False - if type(rootObjects[0]) != QQuickWindow and qquick_present: + if isinstance(rootObjects[0], QQuickWindow) and qquick_present: logging.info("qml: loading with QQuickView") viewer = QQuickView() viewer.setSource(qml_file) diff --git a/sources/pyside-tools/qtpy2cpp_lib/tests/baseline/basic_test.py b/sources/pyside-tools/qtpy2cpp_lib/tests/baseline/basic_test.py index 10dc7376..1466ac6b 100644 --- a/sources/pyside-tools/qtpy2cpp_lib/tests/baseline/basic_test.py +++ b/sources/pyside-tools/qtpy2cpp_lib/tests/baseline/basic_test.py @@ -4,7 +4,7 @@ import sys -from PySide6.QtCore import qVersion, Qt +from PySide6.QtCore import Qt from PySide6.QtGui import QColor, QPainter, QPaintEvent, QShortcut from PySide6.QtWidgets import QApplication, QWidget diff --git a/sources/pyside-tools/qtpy2cpp_lib/tests/test_qtpy2cpp.py b/sources/pyside-tools/qtpy2cpp_lib/tests/test_qtpy2cpp.py index f9f92170..894b2a95 100644 --- a/sources/pyside-tools/qtpy2cpp_lib/tests/test_qtpy2cpp.py +++ b/sources/pyside-tools/qtpy2cpp_lib/tests/test_qtpy2cpp.py @@ -14,7 +14,7 @@ def diff_code(actual_code, expected_file): with tempfile.NamedTemporaryFile(suffix=".cpp") as tf: tf.write(actual_code.encode('utf-8')) tf.flush() - diff_cmd = ["diff", "-u", expected_file, tf.name] + diff_cmd = ["diff", "-u", expected_file, tf.name] subprocess.run(diff_cmd) @@ -33,22 +33,22 @@ def run_converter(tool, file): def test_examples(): dir = Path(__file__).resolve().parent tool = dir.parents[1] / "qtpy2cpp.py" - assert(tool.is_file) + assert tool.is_file for test_file in (dir / "baseline").glob("*.py"): - assert(test_file.is_file) + assert test_file.is_file expected_file = test_file.parent / (test_file.stem + ".cpp") if expected_file.is_file(): actual_code = run_converter(tool, test_file) - assert(actual_code) + assert actual_code expected_code = expected_file.read_text() # Strip the license code_start = expected_code.find("// Converted from") - assert(code_start != -1) + assert code_start != -1 expected_code = expected_code[code_start:] if actual_code != expected_code: diff_code(actual_code, expected_file) - assert(actual_code == expected_code) + assert actual_code == expected_code else: print(f"Warning, {test_file} is missing a .cpp file.", file=sys.stderr) diff --git a/sources/pyside-tools/qtpy2cpp_lib/visitor.py b/sources/pyside-tools/qtpy2cpp_lib/visitor.py index 1e8b5dc8..2056951a 100644 --- a/sources/pyside-tools/qtpy2cpp_lib/visitor.py +++ b/sources/pyside-tools/qtpy2cpp_lib/visitor.py @@ -10,8 +10,7 @@ import warnings from .formatter import (CppFormatter, format_for_loop, format_literal, format_name_constant, - format_reference, format_start_function_call, - write_import, write_import_from) + format_reference, write_import, write_import_from) from .nodedump import debug_format_node from .qt import ClassFlag, qt_class_flags diff --git a/sources/pyside-tools/requirements-android.txt b/sources/pyside-tools/requirements-android.txt new file mode 100644 index 00000000..1169fd66 --- /dev/null +++ b/sources/pyside-tools/requirements-android.txt @@ -0,0 +1,2 @@ +jinja2 +pkginfo diff --git a/sources/pyside6/.cmake.conf b/sources/pyside6/.cmake.conf index ec46c853..66542fa5 100644 --- a/sources/pyside6/.cmake.conf +++ b/sources/pyside6/.cmake.conf @@ -1,5 +1,5 @@ set(pyside_MAJOR_VERSION "6") set(pyside_MINOR_VERSION "6") -set(pyside_MICRO_VERSION "1") +set(pyside_MICRO_VERSION "2") set(pyside_PRE_RELEASE_VERSION_TYPE "") set(pyside_PRE_RELEASE_VERSION "") diff --git a/sources/pyside6/CMakeLists.txt b/sources/pyside6/CMakeLists.txt index 2e4c7010..f45c0711 100644 --- a/sources/pyside6/CMakeLists.txt +++ b/sources/pyside6/CMakeLists.txt @@ -14,9 +14,9 @@ include(cmake/PySideSetup.cmake) get_rpath_base_token(base) if (${STANDALONE}) - set(CMAKE_INSTALL_RPATH ${base}/ ${base}/Qt/lib) + set(CMAKE_INSTALL_RPATH ${base}/ ${base}/Qt/lib ${base}/../shiboken6/) else() - set(CMAKE_INSTALL_RPATH ${base}/ ${QT6_INSTALL_PREFIX}/${QT6_INSTALL_LIBS}) + set(CMAKE_INSTALL_RPATH ${base}/ ${QT6_INSTALL_PREFIX}/${QT6_INSTALL_LIBS} ${base}/../shiboken6/) endif() add_subdirectory(libpyside) diff --git a/sources/pyside6/PySide6/QtAsyncio/__init__.py b/sources/pyside6/PySide6/QtAsyncio/__init__.py index 2c673e40..3830ad88 100644 --- a/sources/pyside6/PySide6/QtAsyncio/__init__.py +++ b/sources/pyside6/PySide6/QtAsyncio/__init__.py @@ -7,8 +7,41 @@ from .events import ( from .futures import QAsyncioFuture from .tasks import QAsyncioTask +import asyncio +import typing + __all__ = [ "QAsyncioEventLoopPolicy", "QAsyncioEventLoop", "QAsyncioHandle", "QAsyncioTimerHandle", "QAsyncioFuture", "QAsyncioTask" ] + + +def run(coro: typing.Optional[typing.Coroutine] = None, + keep_running: bool = True, + quit_qapp: bool = True, *, + debug: typing.Optional[bool] = None) -> None: + """Run the QtAsyncio event loop.""" + + # Event loop policies are expected to be deprecated with Python 3.13, with + # subsequent removal in Python 3.15. At that point, part of the current + # logic of the QAsyncioEventLoopPolicy constructor will have to be moved + # here and/or to a loop factory class (to be provided as an argument to + # asyncio.run()), namely setting up the QCoreApplication and the SIGINT + # handler. + # + # More details: + # https://discuss.python.org/t/removing-the-asyncio-policy-system-asyncio-set-event-loop-policy-in-python-3-15/37553 # noqa: E501 + asyncio.set_event_loop_policy(QAsyncioEventLoopPolicy(quit_qapp=quit_qapp)) + + if keep_running: + if coro: + asyncio.ensure_future(coro) + asyncio.get_event_loop().run_forever() + else: + if coro: + asyncio.run(coro, debug=debug) + else: + raise RuntimeError( + "QtAsyncio was set to keep running after the coroutine " + "finished, but no coroutine was provided.") diff --git a/sources/pyside6/PySide6/QtAsyncio/events.py b/sources/pyside6/PySide6/QtAsyncio/events.py index edd42646..aa89e984 100644 --- a/sources/pyside6/PySide6/QtAsyncio/events.py +++ b/sources/pyside6/PySide6/QtAsyncio/events.py @@ -1,7 +1,8 @@ # Copyright (C) 2023 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only -from PySide6.QtCore import QCoreApplication, QDateTime, QEventLoop, QObject, QTimer, QThread, Slot +from PySide6.QtCore import (QCoreApplication, QDateTime, QDeadlineTimer, + QEventLoop, QObject, QTimer, QThread, Slot) from . import futures from . import tasks @@ -12,9 +13,11 @@ import concurrent.futures import contextvars import enum import os +import signal import socket import subprocess import typing +import warnings __all__ = [ "QAsyncioEventLoopPolicy", "QAsyncioEventLoop", @@ -54,7 +57,9 @@ class QAsyncioExecutorWrapper(QObject): class QAsyncioEventLoopPolicy(asyncio.AbstractEventLoopPolicy): - def __init__(self, application: typing.Optional[QCoreApplication] = None) -> None: + def __init__(self, + application: typing.Optional[QCoreApplication] = None, + quit_qapp: bool = True) -> None: super().__init__() if application is None: if QCoreApplication.instance() is None: @@ -62,8 +67,11 @@ class QAsyncioEventLoopPolicy(asyncio.AbstractEventLoopPolicy): else: application = QCoreApplication.instance() self._application: QCoreApplication = application # type: ignore[assignment] + self._quit_qapp = quit_qapp self._event_loop: typing.Optional[asyncio.AbstractEventLoop] = None + signal.signal(signal.SIGINT, signal.SIG_DFL) + def get_event_loop(self) -> asyncio.AbstractEventLoop: if self._event_loop is None: self._event_loop = QAsyncioEventLoop(self._application) @@ -73,7 +81,13 @@ class QAsyncioEventLoopPolicy(asyncio.AbstractEventLoopPolicy): self._event_loop = loop def new_event_loop(self) -> asyncio.AbstractEventLoop: - return QAsyncioEventLoop(self._application) + return QAsyncioEventLoop(self._application, quit_qapp=self._quit_qapp) + + def get_child_watcher(self) -> asyncio.AbstractChildWatcher: + raise DeprecationWarning("Child watchers are deprecated since Python 3.12") + + def set_child_watcher(self, watcher: asyncio.AbstractChildWatcher) -> None: + raise DeprecationWarning("Child watchers are deprecated since Python 3.12") class QAsyncioEventLoop(asyncio.BaseEventLoop, QObject): @@ -101,11 +115,13 @@ class QAsyncioEventLoop(asyncio.BaseEventLoop, QObject): if not self._loop.is_closed(): self._loop.call_soon_threadsafe(self._future.set_exception, e) - def __init__(self, application: QCoreApplication) -> None: + def __init__(self, + application: QCoreApplication, quit_qapp: bool = True) -> None: asyncio.BaseEventLoop.__init__(self) QObject.__init__(self) self._application: QCoreApplication = application + self._quit_qapp = quit_qapp self._thread = QThread.currentThread() self._closed = False @@ -135,7 +151,8 @@ class QAsyncioEventLoop(asyncio.BaseEventLoop, QObject): return future.get_loop().stop() - def run_until_complete(self, future: futures.QAsyncioFuture) -> typing.Any: # type: ignore[override] + def run_until_complete(self, + future: futures.QAsyncioFuture) -> typing.Any: # type: ignore[override] if self.is_closed(): raise RuntimeError("Event loop is closed") if self.is_running(): @@ -179,7 +196,8 @@ class QAsyncioEventLoop(asyncio.BaseEventLoop, QObject): else: return self._quit_from_inside = True - self._application.quit() + if self._quit_qapp: + self._application.quit() def is_running(self) -> bool: return self._thread.loopLevel() > 0 @@ -194,7 +212,8 @@ class QAsyncioEventLoop(asyncio.BaseEventLoop, QObject): return if self._default_executor is not None: self._default_executor.shutdown(wait=False) - self._application.shutdown() + if self._quit_qapp: + self._application.shutdown() self._closed = True async def shutdown_asyncgens(self) -> None: @@ -215,7 +234,14 @@ class QAsyncioEventLoop(asyncio.BaseEventLoop, QObject): self._asyncgens.clear() - async def shutdown_default_executor(self) -> None: + async def shutdown_default_executor(self, # type: ignore[override] + timeout: typing.Union[int, float, None] = None) -> None: + shutdown_successful = False + if timeout is not None: + deadline_timer = QDeadlineTimer(int(timeout * 1000)) + else: + deadline_timer = QDeadlineTimer(QDeadlineTimer.Forever) + if self._default_executor is None: return future = self.create_future() @@ -224,39 +250,61 @@ class QAsyncioEventLoop(asyncio.BaseEventLoop, QObject): try: await future finally: - thread.wait() + shutdown_successful = thread.wait(deadline_timer) + + if timeout is not None and not shutdown_successful: + warnings.warn( + f"Could not shutdown the default executor within {timeout} seconds", + RuntimeWarning, stacklevel=2) + self._default_executor.shutdown(wait=False) # Scheduling callbacks + def _call_soon_impl(self, callback: typing.Callable, *args: typing.Any, + context: typing.Optional[contextvars.Context] = None, + is_threadsafe: typing.Optional[bool] = False) -> asyncio.Handle: + return self._call_later_impl(0, callback, *args, context=context, + is_threadsafe=is_threadsafe) + def call_soon(self, callback: typing.Callable, *args: typing.Any, - context: typing.Optional[contextvars.Context] = None): - return self.call_later(0, callback, *args, context=context) + context: typing.Optional[contextvars.Context] = None) -> asyncio.Handle: + return self._call_soon_impl(callback, *args, context=context, is_threadsafe=False) - def call_soon_threadsafe(self, callback: typing.Callable, # type: ignore[override] - *args: typing.Any, + def call_soon_threadsafe(self, callback: typing.Callable, *args: typing.Any, context: - typing.Optional[contextvars.Context] = None) -> "QAsyncioHandle": - if self.is_closed(): - raise RuntimeError("Event loop is closed") + typing.Optional[contextvars.Context] = None) -> asyncio.Handle: if context is None: context = contextvars.copy_context() - return self.call_soon(callback, *args, context=context) + return self._call_soon_impl(callback, *args, context=context, is_threadsafe=True) - def call_later(self, delay: typing.Union[int, float], # type: ignore[override] - callback: typing.Callable, *args: typing.Any, - context: typing.Optional[contextvars.Context] = None) -> "QAsyncioHandle": + def _call_later_impl(self, delay: typing.Union[int, float], + callback: typing.Callable, *args: typing.Any, + context: typing.Optional[contextvars.Context] = None, + is_threadsafe: typing.Optional[bool] = False) -> asyncio.TimerHandle: if not isinstance(delay, (int, float)): raise TypeError("delay must be an int or float") - return self.call_at(self.time() + delay, callback, *args, context=context) + return self._call_at_impl(self.time() + delay, callback, *args, context=context, + is_threadsafe=is_threadsafe) - def call_at(self, when: typing.Union[int, float], # type: ignore[override] - callback: typing.Callable, *args: typing.Any, - context: typing.Optional[contextvars.Context] = None) -> "QAsyncioHandle": + def call_later(self, delay: typing.Union[int, float], + callback: typing.Callable, *args: typing.Any, + context: typing.Optional[contextvars.Context] = None) -> asyncio.TimerHandle: + return self._call_later_impl(delay, callback, *args, context=context, is_threadsafe=False) + + def _call_at_impl(self, when: typing.Union[int, float], + callback: typing.Callable, *args: typing.Any, + context: typing.Optional[contextvars.Context] = None, + is_threadsafe: typing.Optional[bool] = False) -> asyncio.TimerHandle: if not isinstance(when, (int, float)): raise TypeError("when must be an int or float") if self.is_closed(): raise RuntimeError("Event loop is closed") - return QAsyncioTimerHandle(when, callback, args, self, context) + return QAsyncioTimerHandle(when, callback, args, self, context, is_threadsafe=is_threadsafe) + + def call_at(self, when: typing.Union[int, float], + callback: typing.Callable, *args: typing.Any, + context: typing.Optional[contextvars.Context] = None) -> asyncio.TimerHandle: + return self._call_at_impl(when, callback, *args, context=context, is_threadsafe=False) def time(self) -> float: return QDateTime.currentMSecsSinceEpoch() / 1000 @@ -459,7 +507,8 @@ class QAsyncioEventLoop(asyncio.BaseEventLoop, QObject): def default_exception_handler(self, context: typing.Dict[str, typing.Any]) -> None: # TODO - print(context["message"]) + if context["message"]: + print(context["message"]) def call_exception_handler(self, context: typing.Dict[str, typing.Any]) -> None: if self._exception_handler is not None: @@ -499,11 +548,13 @@ class QAsyncioHandle(): DONE = enum.auto() def __init__(self, callback: typing.Callable, args: typing.Tuple, - loop: QAsyncioEventLoop, context: typing.Optional[contextvars.Context]) -> None: + loop: QAsyncioEventLoop, context: typing.Optional[contextvars.Context], + is_threadsafe: typing.Optional[bool] = False) -> None: self._callback = callback self._args = args self._loop = loop self._context = context + self._is_threadsafe = is_threadsafe self._timeout = 0 @@ -512,7 +563,10 @@ class QAsyncioHandle(): def _schedule_event(self, timeout: int, func: typing.Callable) -> None: if not self._loop.is_closed() and not self._loop._quit_from_outside: - QTimer.singleShot(timeout, self._loop, func) + if self._is_threadsafe: + QTimer.singleShot(timeout, self._loop, func) + else: + QTimer.singleShot(timeout, func) def _start(self) -> None: self._schedule_event(self._timeout, lambda: self._cb()) @@ -535,15 +589,16 @@ class QAsyncioHandle(): return self._state == QAsyncioHandle.HandleState.CANCELLED -class QAsyncioTimerHandle(QAsyncioHandle): +class QAsyncioTimerHandle(QAsyncioHandle, asyncio.TimerHandle): def __init__(self, when: float, callback: typing.Callable, args: typing.Tuple, - loop: QAsyncioEventLoop, context: typing.Optional[contextvars.Context]) -> None: - super().__init__(callback, args, loop, context) + loop: QAsyncioEventLoop, context: typing.Optional[contextvars.Context], + is_threadsafe: typing.Optional[bool] = False) -> None: + QAsyncioHandle.__init__(self, callback, args, loop, context, is_threadsafe) self._when = when self._timeout = int(max(self._when - self._loop.time(), 0) * 1000) - super()._start() + QAsyncioHandle._start(self) # Override this so that timer.start() is only called once at the end # of the constructor for both QtHandle and QtTimerHandle. diff --git a/sources/pyside6/PySide6/QtAsyncio/futures.py b/sources/pyside6/PySide6/QtAsyncio/futures.py index 7ed8bcb6..611bd563 100644 --- a/sources/pyside6/PySide6/QtAsyncio/futures.py +++ b/sources/pyside6/PySide6/QtAsyncio/futures.py @@ -25,15 +25,16 @@ class QAsyncioFuture(): def __init__(self, *, loop: typing.Optional["events.QAsyncioEventLoop"] = None, context: typing.Optional[contextvars.Context] = None) -> None: + self._loop: "events.QAsyncioEventLoop" if loop is None: - self._loop = asyncio.events.get_event_loop() + self._loop = asyncio.events.get_event_loop() # type: ignore[assignment] else: self._loop = loop self._context = context self._state = QAsyncioFuture.FutureState.PENDING self._result: typing.Any = None - self._exception: typing.Optional[Exception] = None + self._exception: typing.Optional[BaseException] = None self._callbacks: typing.List[typing.Callable] = list() @@ -44,16 +45,15 @@ class QAsyncioFuture(): self._asyncio_future_blocking = True yield self if not self.done(): - raise RuntimeError("await was not used with a Future") + raise RuntimeError("await was not used with a Future or Future-like object") return self.result() __iter__ = __await__ def _schedule_callbacks(self, context: typing.Optional[contextvars.Context] = None): - if self._loop.is_running(): - for cb in self._callbacks: - self._loop.call_soon( - cb, self, context=context if context else self._context) + for cb in self._callbacks: + self._loop.call_soon( + cb, self, context=context if context else self._context) def result(self) -> typing.Union[typing.Any, Exception]: if self._state == QAsyncioFuture.FutureState.DONE_WITH_RESULT: @@ -96,14 +96,15 @@ class QAsyncioFuture(): self._callbacks = [_cb for _cb in self._callbacks if _cb != cb] return original_len - len(self._callbacks) - def cancel(self) -> bool: + def cancel(self, msg: typing.Optional[str] = None) -> bool: if self.done(): return False self._state = QAsyncioFuture.FutureState.CANCELLED + self._cancel_message = msg self._schedule_callbacks() return True - def exception(self) -> typing.Optional[Exception]: + def exception(self) -> typing.Optional[BaseException]: if self._state == QAsyncioFuture.FutureState.CANCELLED: raise asyncio.CancelledError if self.done(): diff --git a/sources/pyside6/PySide6/QtAsyncio/tasks.py b/sources/pyside6/PySide6/QtAsyncio/tasks.py index 78f9dfb0..bc3d41a7 100644 --- a/sources/pyside6/PySide6/QtAsyncio/tasks.py +++ b/sources/pyside6/PySide6/QtAsyncio/tasks.py @@ -27,6 +27,9 @@ class QAsyncioTask(futures.QAsyncioFuture): self._cancellation_requests = 0 + self._future_to_await: typing.Optional[asyncio.Future] = None + self._cancel_message: typing.Optional[str] = None + asyncio._register_task(self) # type: ignore[arg-type] def __repr__(self) -> str: @@ -44,14 +47,6 @@ class QAsyncioTask(futures.QAsyncioFuture): class QtTaskApiMisuseError(Exception): pass - def __await__(self) -> None: # type: ignore[override] - # This function is not inherited from the Future APIs. - raise QAsyncioTask.QtTaskApiMisuseError("Tasks cannot be awaited") - - def __iter__(self) -> None: # type: ignore[override] - # This function is not inherited from the Future APIs. - raise QAsyncioTask.QtTaskApiMisuseError("Tasks cannot be iterated over") - def set_result(self, result: typing.Any) -> None: # type: ignore[override] # This function is not inherited from the Future APIs. raise QAsyncioTask.QtTaskApiMisuseError("Tasks cannot set results") @@ -66,6 +61,7 @@ class QAsyncioTask(futures.QAsyncioFuture): if self.done(): return result = None + self._future_to_await = None try: asyncio._enter_task(self._loop, self) # type: ignore[arg-type] @@ -83,16 +79,17 @@ class QAsyncioTask(futures.QAsyncioFuture): except StopIteration as e: self._state = futures.QAsyncioFuture.FutureState.DONE_WITH_RESULT self._result = e.value - except concurrent.futures.CancelledError as e: + except (concurrent.futures.CancelledError, asyncio.exceptions.CancelledError) as e: self._state = futures.QAsyncioFuture.FutureState.CANCELLED self._exception = e except BaseException as e: self._state = futures.QAsyncioFuture.FutureState.DONE_WITH_EXCEPTION - self._exception = e # type: ignore[assignment] + self._exception = e else: if asyncio.futures.isfuture(result): result.add_done_callback( self._step, context=self._context) # type: ignore[arg-type] + self._future_to_await = result elif result is None: self._loop.call_soon(self._step, context=self._context) else: @@ -137,7 +134,8 @@ class QAsyncioTask(futures.QAsyncioFuture): return False self._cancel_message = msg self._handle.cancel() - self._state = futures.QAsyncioFuture.FutureState.CANCELLED + if self._future_to_await is not None: + self._future_to_await.cancel(msg) return True def uncancel(self) -> None: diff --git a/sources/pyside6/PySide6/QtCharts/CMakeLists.txt b/sources/pyside6/PySide6/QtCharts/CMakeLists.txt index 5168991f..f7a4d3cc 100644 --- a/sources/pyside6/PySide6/QtCharts/CMakeLists.txt +++ b/sources/pyside6/PySide6/QtCharts/CMakeLists.txt @@ -25,6 +25,7 @@ ${QtCharts_GEN_DIR}/qcandlestickset_wrapper.cpp ${QtCharts_GEN_DIR}/qcategoryaxis_wrapper.cpp ${QtCharts_GEN_DIR}/qchart_wrapper.cpp ${QtCharts_GEN_DIR}/qchartview_wrapper.cpp +${QtCharts_GEN_DIR}/qcoloraxis_wrapper.cpp ${QtCharts_GEN_DIR}/qdatetimeaxis_wrapper.cpp ${QtCharts_GEN_DIR}/qhbarmodelmapper_wrapper.cpp ${QtCharts_GEN_DIR}/qhboxplotmodelmapper_wrapper.cpp diff --git a/sources/pyside6/PySide6/QtCharts/typesystem_charts.xml b/sources/pyside6/PySide6/QtCharts/typesystem_charts.xml index f271b776..a9cd7317 100644 --- a/sources/pyside6/PySide6/QtCharts/typesystem_charts.xml +++ b/sources/pyside6/PySide6/QtCharts/typesystem_charts.xml @@ -160,6 +160,7 @@ + diff --git a/sources/pyside6/PySide6/QtCore/typesystem_core_common.xml b/sources/pyside6/PySide6/QtCore/typesystem_core_common.xml index 2011bd3e..5168373f 100644 --- a/sources/pyside6/PySide6/QtCore/typesystem_core_common.xml +++ b/sources/pyside6/PySide6/QtCore/typesystem_core_common.xml @@ -116,6 +116,9 @@ + @@ -1728,16 +1731,10 @@ - - - - - - @@ -1747,16 +1744,20 @@ - + + + - - @@ -1764,12 +1765,17 @@ - + - + + + + + + + diff --git a/sources/pyside6/PySide6/QtDBus/typesystem_dbus.xml b/sources/pyside6/PySide6/QtDBus/typesystem_dbus.xml index 8580c339..02b26d7e 100644 --- a/sources/pyside6/PySide6/QtDBus/typesystem_dbus.xml +++ b/sources/pyside6/PySide6/QtDBus/typesystem_dbus.xml @@ -53,6 +53,8 @@ + + @@ -100,7 +102,7 @@ - + diff --git a/sources/pyside6/PySide6/QtGui/CMakeLists.txt b/sources/pyside6/PySide6/QtGui/CMakeLists.txt index 0f5fcd47..da150af7 100644 --- a/sources/pyside6/PySide6/QtGui/CMakeLists.txt +++ b/sources/pyside6/PySide6/QtGui/CMakeLists.txt @@ -11,6 +11,7 @@ set(QtGui_DROPPED_ENTRIES) set(QtGui_SRC_UNITY_EXCLUDED_SRC ${QtGui_GEN_DIR}/qtextframe_iterator_wrapper.cpp ${QtGui_GEN_DIR}/qtextblock_iterator_wrapper.cpp + ${QtGui_GEN_DIR}/qshaderversion_wrapper.cpp ) set_property(SOURCE ${QtGui_SRC_UNITY_EXCLUDED_SRC} @@ -64,7 +65,6 @@ ${QtGui_GEN_DIR}/qrhiviewport_wrapper.cpp ${QtGui_GEN_DIR}/qshader_wrapper.cpp ${QtGui_GEN_DIR}/qshadercode_wrapper.cpp ${QtGui_GEN_DIR}/qshaderkey_wrapper.cpp -${QtGui_GEN_DIR}/qshaderversion_wrapper.cpp ) if (ENABLE_WIN) diff --git a/sources/pyside6/PySide6/QtSql/typesystem_sql.xml b/sources/pyside6/PySide6/QtSql/typesystem_sql.xml index b77ab0b3..022a5ab0 100644 --- a/sources/pyside6/PySide6/QtSql/typesystem_sql.xml +++ b/sources/pyside6/PySide6/QtSql/typesystem_sql.xml @@ -123,7 +123,7 @@ + QTBUG-91766/PYSIDE-2394. allow-thread for PYSIDE-1931 --> @@ -137,6 +137,7 @@ + @@ -169,6 +170,7 @@ + diff --git a/sources/pyside6/PySide6/glue/qtcore.cpp b/sources/pyside6/PySide6/glue/qtcore.cpp index 52a07aa2..94dc915a 100644 --- a/sources/pyside6/PySide6/glue/qtcore.cpp +++ b/sources/pyside6/PySide6/glue/qtcore.cpp @@ -321,6 +321,12 @@ PyModule_AddStringConstant(module, "__version__", qVersion()); %PYARG_0 = %CONVERTTOPYTHON[%RETURN_TYPE](%0); // @snippet qobject-connect-4 +// @snippet qobject-connect-4-context +// %FUNCTION_NAME() - disable generation of function call. +%RETURN_TYPE %0 = PySide::qobjectConnectCallback(%1, %2, %3, %PYARG_4, %5); +%PYARG_0 = %CONVERTTOPYTHON[%RETURN_TYPE](%0); +// @snippet qobject-connect-4-context + // @snippet qobject-connect-5 // %FUNCTION_NAME() - disable generation of function call. %RETURN_TYPE %0 = PySide::qobjectConnectCallback(%CPPSELF, %1, %PYARG_2, %3); @@ -430,8 +436,7 @@ if (%PYARG_0 == Py_None) namespace PySide { template<> inline Py_ssize_t hash(const QLine &l) { - const int v[4] = {l.x1(), l.y1(), l.x2(), l.y2()}; - return qHashRange(v, v + 4); + return qHashMulti(0, l.x1(), l.y1(), l.x2(), l.y2()); } }; // @snippet qline-hash @@ -1054,28 +1059,9 @@ timer->start(%1); // @snippet qtimer-singleshot-2 // @snippet qtimer-singleshot-functor-context -Shiboken::AutoDecRef emptyTuple(PyTuple_New(0)); -if (PyObject_TypeCheck(%3, PySideSignalInstance_TypeF())) { - auto *timerType = Shiboken::SbkType(); - auto *pyTimer = timerType->tp_new(Shiboken::SbkType(), emptyTuple, nullptr); - timerType->tp_init(pyTimer, emptyTuple, nullptr); - QTimer * timer = %CONVERTTOCPP[QTimer *](pyTimer); - timer->setSingleShot(true); - PySideSignalInstance *signalInstance = reinterpret_cast(%2); - Shiboken::AutoDecRef signalSignature(Shiboken::String::fromFormat("2%s", PySide::Signal::getSignature(signalInstance))); - Shiboken::AutoDecRef result( - PyObject_CallFunction(PySide::PySideName::qtConnect(), "OsOO", - pyTimer, - SIGNAL(timeout()), - %3, - PySide::Signal::getObject(signalInstance), - signalSignature.object()) - ); - timer->connect(timer, &QTimer::timeout, timer, &QObject::deleteLater, Qt::DirectConnection); - Shiboken::Object::releaseOwnership(reinterpret_cast(pyTimer)); - Py_XDECREF(pyTimer); - timer->start(%1); -} else { +auto msec = %1; +if (msec == 0) { + Shiboken::AutoDecRef emptyTuple(PyTuple_New(0)); auto *callable = %PYARG_3; auto cppCallback = [callable]() { @@ -1086,7 +1072,30 @@ if (PyObject_TypeCheck(%3, PySideSignalInstance_TypeF())) { }; Py_INCREF(callable); - %CPPSELF.%FUNCTION_NAME(%1, %2, cppCallback); + %CPPSELF.%FUNCTION_NAME(msec, %2, cppCallback); +} else { + Shiboken::AutoDecRef emptyTuple(PyTuple_New(0)); + auto *timerType = Shiboken::SbkType(); + auto newFunc = timerType->tp_new; + auto initFunc = timerType->tp_init; + auto *pyTimer = newFunc(Shiboken::SbkType(), emptyTuple, nullptr); + initFunc(pyTimer, emptyTuple, nullptr); + + QTimer * timer = %CONVERTTOCPP[QTimer *](pyTimer); + timer->setSingleShot(true); + + Shiboken::AutoDecRef result( + PyObject_CallMethod(pyTimer, "connect", "OsOO", + pyTimer, + SIGNAL(timeout()), + %PYARG_2, + %PYARG_3) + ); + + timer->connect(timer, &QTimer::timeout, timer, &QObject::deleteLater, Qt::DirectConnection); + Shiboken::Object::releaseOwnership(reinterpret_cast(pyTimer)); + Py_XDECREF(pyTimer); + timer->start(msec); } // @snippet qtimer-singleshot-functor-context @@ -1440,7 +1449,7 @@ auto res = (*%CPPSELF) + cppArg0; // @snippet conversion-pystring-char char c = %CONVERTTOCPP[char](%in); -%out = %OUTTYPE(c); +%out = %OUTTYPE(static_cast(c)); // @snippet conversion-pystring-char // @snippet conversion-pyint @@ -1921,7 +1930,7 @@ auto callback = [callable]() -> void return; } Shiboken::GilState state; - PyObject_CallObject(callable, nullptr); + Shiboken::AutoDecRef ret(PyObject_CallObject(callable, nullptr)); Py_DECREF(callable); }; Py_INCREF(callable); @@ -2008,9 +2017,9 @@ auto callback = [callable, count, arg_qpermission](const QPermission &permission if (arg_qpermission) { Shiboken::AutoDecRef arglist(PyTuple_New(1)); PyTuple_SET_ITEM(arglist.object(), 0, %CONVERTTOPYTHON[QPermission](permission)); - PyObject_CallObject(callable, arglist); + Shiboken::AutoDecRef ret(PyObject_CallObject(callable, arglist)); } else { - PyObject_CallObject(callable, nullptr); + Shiboken::AutoDecRef ret(PyObject_CallObject(callable, nullptr)); } Py_DECREF(callable); }; diff --git a/sources/pyside6/PySide6/glue/qtnetworkauth.cpp b/sources/pyside6/PySide6/glue/qtnetworkauth.cpp index e22569e2..f390605d 100644 --- a/sources/pyside6/PySide6/glue/qtnetworkauth.cpp +++ b/sources/pyside6/PySide6/glue/qtnetworkauth.cpp @@ -16,13 +16,15 @@ auto callback = [callable](QAbstractOAuth::Stage stage, QMultiMap](dict)); Shiboken::AutoDecRef ret(PyObject_CallObject(callable, arglist)); - PyObject *key; - PyObject *value; - Py_ssize_t pos = 0; - while (PyDict_Next(ret, &pos, &key, &value)) { - QString cppKey = %CONVERTTOCPP[QString](key); - QVariant cppValue = %CONVERTTOCPP[QVariant](value); - dictPointer->replace(cppKey, cppValue); + if (!ret.isNull() && PyDict_Check(ret.object()) != 0) { + PyObject *key{}; + PyObject *value{}; + Py_ssize_t pos = 0; + while (PyDict_Next(ret.object(), &pos, &key, &value)) { + QString cppKey = %CONVERTTOCPP[QString](key); + QVariant cppValue = %CONVERTTOCPP[QVariant](value); + dictPointer->replace(cppKey, cppValue); + } } Py_DECREF(callable); diff --git a/sources/pyside6/PySide6/glue/qtuitools.cpp b/sources/pyside6/PySide6/glue/qtuitools.cpp index 9e52436e..c87cf9e7 100644 --- a/sources/pyside6/PySide6/glue/qtuitools.cpp +++ b/sources/pyside6/PySide6/glue/qtuitools.cpp @@ -62,7 +62,7 @@ Q_IMPORT_PLUGIN(PyCustomWidgets); // @snippet quiloader-registercustomwidget registerCustomWidget(%PYARG_1); -%CPPSELF.addPluginPath(""); // force reload widgets +%CPPSELF.addPluginPath(QString{}); // force reload widgets // @snippet quiloader-registercustomwidget // @snippet quiloader-load-1 @@ -87,7 +87,7 @@ char *arg1 = PyBytes_AsString(strObj); QByteArray uiFileName(arg1); Py_DECREF(strObj); -QFile uiFile(uiFileName); +QFile uiFile(QString::fromUtf8(uiFileName)); if (!uiFile.exists()) { qCritical().noquote() << "File" << uiFileName << "does not exists"; @@ -102,7 +102,7 @@ if (uiFileName.isEmpty()) { // Use the 'pyside6-uic' wrapper instead of 'uic' // This approach is better than rely on 'uic' since installing // the wheels cover this case. -QString uicBin("pyside6-uic"); +QString uicBin(QStringLiteral("pyside6-uic")); QStringList uicArgs = {QString::fromUtf8(uiFileName)}; QProcess uicProcess; diff --git a/sources/pyside6/PySide6/glue/qtwebenginecore.cpp b/sources/pyside6/PySide6/glue/qtwebenginecore.cpp index a569e6c1..0a0a00ec 100644 --- a/sources/pyside6/PySide6/glue/qtwebenginecore.cpp +++ b/sources/pyside6/PySide6/glue/qtwebenginecore.cpp @@ -10,11 +10,11 @@ auto callback = [callable](const QWebEngineCookieStore::FilterRequest& filterReq PyTuple_SET_ITEM(arglist, 0, %CONVERTTOPYTHON[QWebEngineCookieStore::FilterRequest](filterRequest)); Py_INCREF(callable); - PyObject* ret = PyObject_CallObject(callable, arglist); + Shiboken::AutoDecRef ret(PyObject_CallObject(callable, arglist)); Py_DECREF(callable); - return ret; - + return ret.object() == Py_True; }; + %CPPSELF.%FUNCTION_NAME(callback); // @snippet qwebenginecookiestore-setcookiefilter @@ -24,13 +24,13 @@ auto callback = [callable](std::unique_ptr webEngineNoti { Shiboken::GilState state; Shiboken::AutoDecRef arglist(PyTuple_New(1)); + auto *notification = webEngineNotification.release(); PyTuple_SET_ITEM(arglist.object(), 0, - Shiboken::Conversions::pointerToPython( - SbkPySide6_QtWebEngineCoreTypes[SBK_QWEBENGINENOTIFICATION_IDX], - webEngineNotification.release())); + %CONVERTTOPYTHON[QWebEngineNotification*](notification)); Py_INCREF(callable); - PyObject_CallObject(callable, arglist); + Shiboken::AutoDecRef ret(PyObject_CallObject(callable, arglist)); Py_DECREF(callable); }; + %CPPSELF.%FUNCTION_NAME(callback); // @snippet qwebengineprofile-setnotificationpresenter diff --git a/sources/pyside6/PySide6/glue/qtwebenginewidgets.cpp b/sources/pyside6/PySide6/glue/qtwebenginewidgets.cpp index 8fdd6b69..af15130a 100644 --- a/sources/pyside6/PySide6/glue/qtwebenginewidgets.cpp +++ b/sources/pyside6/PySide6/glue/qtwebenginewidgets.cpp @@ -55,8 +55,8 @@ auto callback = [callable](const QString &text) PyTuple_SET_ITEM(arglist, 0, %CONVERTTOPYTHON[QString](text)); Shiboken::AutoDecRef ret(PyObject_CallObject(callable, arglist)); Py_DECREF(callable); - }; + Py_INCREF(callable); %CPPSELF.%FUNCTION_NAME(callback); // @snippet qwebenginepage-convertto @@ -95,8 +95,8 @@ auto callback = [callable](const QVariant &result) // PyTuple_SET_ITEM(arglist, 0, %CONVERTTOPYTHON[bool](found)); Shiboken::AutoDecRef ret(PyObject_CallObject(callable, arglist)); Py_DECREF(callable); - }; + Py_INCREF(callable); %CPPSELF.%FUNCTION_NAME(%1, %2, callback); // @snippet qwebenginepage-runjavascript @@ -114,8 +114,8 @@ auto callback = [callable](const QByteArray &pdf) PyTuple_SET_ITEM(arglist, 0, %CONVERTTOPYTHON[QByteArray](pdf)); Shiboken::AutoDecRef ret(PyObject_CallObject(callable, arglist)); Py_DECREF(callable); - }; + Py_INCREF(callable); %CPPSELF.%FUNCTION_NAME(callback, %2); // @snippet qwebenginepage-printtopdf diff --git a/sources/pyside6/PySide6/templates/gui_common.xml b/sources/pyside6/PySide6/templates/gui_common.xml index c059a2a3..6a740450 100644 --- a/sources/pyside6/PySide6/templates/gui_common.xml +++ b/sources/pyside6/PySide6/templates/gui_common.xml @@ -8,7 +8,7 @@ int size = Shiboken::String::len(%PYARG_1); if (size == 1) { const char *str = Shiboken::String::toCString(%PYARG_1); - QChar ch(str[0]); + const QChar ch(static_cast<unsigned short>(str[0])); %RETURN_TYPE %0 = %CPPSELF.%FUNCTION_NAME(ch); %PYARG_0 = %CONVERTTOPYTHON[%RETURN_TYPE](%0); } else { diff --git a/sources/pyside6/cmake/Macros/PySideModules.cmake b/sources/pyside6/cmake/Macros/PySideModules.cmake index a54f529f..bf6054a7 100644 --- a/sources/pyside6/cmake/Macros/PySideModules.cmake +++ b/sources/pyside6/cmake/Macros/PySideModules.cmake @@ -280,7 +280,7 @@ macro(create_pyside_module) # Don't generate the files when cross-building because the target python can not be executed # on the host machine (usually, unless you use some userspace qemu based mechanism). # TODO: Can we do something better here to still get pyi files? - if(NOT PYSIDE_IS_CROSS_BUILD) + if(NOT (PYSIDE_IS_CROSS_BUILD OR DISABLE_PYI)) set(generate_pyi_options ${module_NAME} --sys-path "${pysidebindings_BINARY_DIR}" "${SHIBOKEN_PYTHON_MODULE_DIR}/..") # use the layer above shiboken6 diff --git a/sources/pyside6/cmake/PySideSetup.cmake b/sources/pyside6/cmake/PySideSetup.cmake index 87a91cfd..038dc164 100644 --- a/sources/pyside6/cmake/PySideSetup.cmake +++ b/sources/pyside6/cmake/PySideSetup.cmake @@ -43,6 +43,10 @@ pyside_internal_set_up_extra_dependency_paths() pyside_internal_find_host_shiboken_tools() find_package(Shiboken6 6 CONFIG REQUIRED) +if(is_pyside6_superproject_build) + shiboken_find_required_python() +endif() + set(BINDING_API_MAJOR_VERSION "${pyside_MAJOR_VERSION}") set(BINDING_API_MINOR_VERSION "${pyside_MINOR_VERSION}") set(BINDING_API_MICRO_VERSION "${pyside_MICRO_VERSION}") @@ -51,7 +55,7 @@ set(BINDING_API_PRE_RELEASE_VERSION "${pyside_PRE_RELEASE_VERSION}") # Detect if the Python interpreter is actually PyPy execute_process( - COMMAND ${PYTHON_EXECUTABLE} -c "if True: + COMMAND ${Python_EXECUTABLE} -c "if True: pypy_version = '' import sys if hasattr(sys, 'pypy_version_info'): diff --git a/sources/pyside6/doc/CMakeLists.txt b/sources/pyside6/doc/CMakeLists.txt index ee37f8aa..bf55263f 100644 --- a/sources/pyside6/doc/CMakeLists.txt +++ b/sources/pyside6/doc/CMakeLists.txt @@ -63,7 +63,6 @@ file(REMOVE ${CMAKE_CURRENT_LIST_DIR}/pyside.qdocconf ${CMAKE_CURRENT_LIST_DIR}/ # for a rst_build_docs case, and not a full doc build if (NOT FULLDOCSBUILD) find_package(Python COMPONENTS Interpreter) - set(PYTHON_EXECUTABLE ${Python_EXECUTABLE}) endif() if (QT_SRC_DIR) @@ -75,7 +74,7 @@ endif() if(PYSIDE_IS_CROSS_BUILD) set(python_executable "${QFP_PYTHON_HOST_PATH}") else() - set(python_executable "${PYTHON_EXECUTABLE}") + set(python_executable "${Python_EXECUTABLE}") endif() set(TOOLS_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../../../tools") @@ -134,7 +133,7 @@ if (FULLDOCSBUILD) file(WRITE ${global_typesystem} "${typeSystemDocXmlContents}") execute_process( - COMMAND ${PYTHON_EXECUTABLE} "${TOOLS_DIR}/doc_modules.py" + COMMAND ${Python_EXECUTABLE} "${TOOLS_DIR}/doc_modules.py" -t "${global_typesystem}" -g "${global_header}" -d "${config_docconf}" "${QT_INCLUDE_DIR}" "${SUPPORTED_QT_VERSION}" OUTPUT_VARIABLE ALL_DOC_MODULES @@ -158,6 +157,7 @@ if (FULLDOCSBUILD) file(APPEND "pyside.qdocconf.in" "\@CMAKE_CURRENT_LIST_DIR\@/${qtdoc_doc_conf}\n") configure_file("pyside.qdocconf.in" "pyside.qdocconf" @ONLY) + configure_file("qdoc_spawner.py.in" "qdoc_spawner.py" @ONLY) set(QDOC_TYPESYSTEM_PATH "${pyside6_SOURCE_DIR}${PATH_SEP}${pyside6_BINARY_DIR}") @@ -172,20 +172,25 @@ if (FULLDOCSBUILD) add_custom_target(qdoc DEPENDS "${DOC_DATA_DIR}/webxml/qtcore-index.webxml") add_custom_command(OUTPUT "${DOC_DATA_DIR}/webxml/qtcore-index.webxml" - # Use dummy Qt version information, QDoc needs it but has no effect on WebXML output - COMMAND ${CMAKE_COMMAND} -E env BUILDDIR=${CMAKE_CURRENT_LIST_DIR}/src QT_INSTALL_DOCS=${QT_SRC_DIR}/doc - QT_VERSION=${QT_VERSION_MAJOR}.${QT_VERSION_MINOR}.${QT_VERSION_PATCH} - QT_VER=${QT_VERSION_MAJOR}.${QT_VERSION_MINOR} - QT_VERSION_TAG=${QT_VERSION_MAJOR}.${QT_VERSION_MINOR}.${QT_VERSION_PATCH} - "${qdoc_binary}" pyside.qdocconf -single-exec - -installdir ${DOC_DATA_DIR} -outputdir ${DOC_DATA_DIR} + Use dummy Qt version information, QDoc needs it but has no effect on WebXML output + COMMAND ${CMAKE_COMMAND} -E env ${python_executable} qdoc_spawner.py + --qt=${QT_VERSION_MAJOR}.${QT_VERSION_MINOR}.${QT_VERSION_PATCH} + --doc-data-dir=${DOC_DATA_DIR} + --qdoc-binary=${qdoc_binary} + --build-dir=${CMAKE_CURRENT_LIST_DIR}/src + --qt-install-docs=${QT_SRC_DIR}/doc + --parallel="auto" + --verbose COMMENT "Running qdoc against Qt source code...") endif() +# Avoid using 'auto' for '-j' option as it could lead to +# crash on some systems due to out-of-memory situation. Instead +# use a no. less than 8. add_custom_target(apidoc COMMAND ${CMAKE_COMMAND} -E env INHERITANCE_FILE=${ENV_INHERITANCE_FILE} ${SHIBOKEN_PYTHON_INTERPRETER} ${SPHINX_BUILD} -b ${DOC_OUTPUT_FORMAT} - -j auto ${CMAKE_CURRENT_BINARY_DIR}/rst html + -j 6 ${CMAKE_CURRENT_BINARY_DIR}/rst html COMMENT "Generating PySide htmls..." ) @@ -198,6 +203,7 @@ if(DOC_OUTPUT_FORMAT STREQUAL "html") ${CMAKE_CURRENT_BINARY_DIR}/../../shiboken6/doc/html ${CMAKE_CURRENT_BINARY_DIR}/html/shiboken6 COMMENT "Copying Shiboken docs..." + DEPENDS "${DOC_DATA_DIR}/webxml/qtcore-index.webxml" VERBATIM) else() if(qhelpgenerator_binary) @@ -209,6 +215,7 @@ else() COMMAND ${python_executable} ${PATCH_QHP_SCRIPT} -p -v pyside6 ${QHP_FILE} COMMAND "${qhelpgenerator_binary}" ${QHP_FILE} COMMENT "Generating QCH from a QHP file..." + DEPENDS "${DOC_DATA_DIR}/webxml/qtcore-index.webxml" VERBATIM ) else() diff --git a/sources/pyside6/doc/PySide6/QtAsyncio/index.rst b/sources/pyside6/doc/PySide6/QtAsyncio/index.rst new file mode 100644 index 00000000..90c0ee67 --- /dev/null +++ b/sources/pyside6/doc/PySide6/QtAsyncio/index.rst @@ -0,0 +1,145 @@ +.. module:: PySide6.QtAsyncio + +PySide6.QtAsyncio +***************** + +.. note:: This module is currently in technical preview. + +The Qt Asyncio module is a pure Python module that allows programs to be +written that use Qt's API in conjunction with `asyncio +`_. asyncio is a popular +Python library for asynchronous programming. It is used in particular +for programs that need to handle many I/O operations from many sources, +such as web servers. More generally, it allows developers to work with +`couroutines `_. +Coroutines can be imagined as "asynchronous functions". In contrast to +Qt's signals and slot mechanism, this allows for asynchronous programs +that are closer in program flow to synchronous programs, as programs no +longer have to be imagined as a series of callbacks. Instead, coroutines +transparently resume and yield at designated spots. + +Consider the following simple coroutine defined with the ``async`` +keyword in front of its definition: + +:: + + async def do_something(): + result = await do_something_asynchronously() + print(result) + +``do_something_asynchronously()`` is a coroutine itself, e.g., an +I/O-heavy operation that would normally block the execution flow in a +synchronous program. Instead, the ``await`` keyword is used to wait for +the result, at which point ``do_something()`` yields and the program +flow transparently switches to the next asynchronous task. When the +result becomes available, the program flow is able to switch back to the +``do_something()`` coroutine, which then resumes and prints the result. + +The asyncio API +^^^^^^^^^^^^^^^ + +asyncio and Qt are both based on an event loop. asyncio provides an API +to replace its default event loop with a custom implementation. +**QtAsyncio** provides such an implementation that uses Qt's event loop, +allowing Qt and asyncio to be used together. + +We consider that this API consists of two levels: + +1. Fundamental infrastructure for event loops and asynchronous + operations, including `futures + `_, + `tasks `_, + `handles `_, + executors, and event loop management functions (see below). +2. A user-facing API for use in applications, including transports and + protocols, network connections, servers, sockets, signals, + subprocesses. + +**QtAsyncio** currently covers the first level. This includes the +following functions, for which the API is identical with QtAsyncio as +with asyncio: + +* `run_until_complete() `_ +* `run_forever() `_ +* `stop() `_ +* `is_running() `_ +* `is_closed() `_ +* `close() `_ +* `shutdown_asyncgens() `_ +* `shutdown_default_executor() `_ +* `call_soon() `_ +* `call_soon_threadsafe() `_ +* `call_later() `_ +* `call_at() `_ +* `time() `_ +* `create_future() `_ +* `create_task() `_ +* `set_task_factory() `_ +* `get_task_factory() `_ + +Also included is the ability to +`run synchronous code in an executor `_ +(``ThreadPoolExecutor``). + +Get started with QtAsyncio +^^^^^^^^^^^^^^^^^^^^^^^^^^ + +To write a program with **QtAsyncio**, first import the module, e.g.: + +:: + + import PySide6.QtAsyncio as QtAsyncio + +**QtAsyncio** provides a function ``run()`` that can be used to run a +specific coroutine until it is complete, or to start the Qt & asyncio +event loop plainly. The former case makes sense if the program flow +starts with said coroutine, the latter case makes sense if a coroutine +is enqueued later in the program flow, e.g., after pressing a button in +the UI. + +:: + + QtAsyncio.run() + +(see the `asyncio "minimal" example `_ +for an instance of this usage) or + +:: + + QtAsyncio.run(my_coroutine()) + +(see the `asyncio "Eratosthenes" example `_) +or + +:: + + QtAsyncio.run(my_coroutine(), keep_running=False) + +to run the coroutine and then stop the event loop upon its completion. +This latter case behaves identically to ``asyncio.run(my_coroutine())``. + +An additional optional argument ``quit_qapp`` can be passed to ``run()`` +to configure whether the QCoreApplication at the core of QtAsyncio +should be shut down when asyncio finishes. A special case where one +would want to disable this is test suites that want to reuse a single +QCoreApplication instance across all unit tests, which would fail if +this instance is shut down every time. The default is ``True``. + +Note that this argument is orthogonal to the ``keep_running`` argument. +``keep_running`` determines if **asyncio** should keep running after the +coroutine has finished, while ``quit_qapp`` determines if the +QCoreApplication should be shut down after asyncio has finished. It is +possible for asyncio to finish while the QCoreApplication is kept alive. + +Coroutines explained +^^^^^^^^^^^^^^^^^^^^ + +Coroutines are functions that can be paused (yield) and resumed. Behind +this simple concept lies a complex mechanism that is abstracted by the +asynchronous framework. This talk presents a diagram that attempts to +illustrate the flow of a coroutine from the moment it's provided to the +async framework until it's completed. + +.. image:: https://img.youtube.com/vi/XuqdTvisqkQ/mqdefault.jpg + :alt: Asynchronous programming with asyncio and Qt + :target: https://www.youtube.com/watch?v=XuqdTvisqkQ diff --git a/sources/pyside6/doc/_static/qtforpython.ico b/sources/pyside6/doc/_static/qtforpython.ico new file mode 100644 index 00000000..2f4d9e9d Binary files /dev/null and b/sources/pyside6/doc/_static/qtforpython.ico differ diff --git a/sources/pyside6/doc/additionaldocs.lst b/sources/pyside6/doc/additionaldocs.lst index f9f2350e..81da5837 100644 --- a/sources/pyside6/doc/additionaldocs.lst +++ b/sources/pyside6/doc/additionaldocs.lst @@ -20,7 +20,6 @@ creating-a-sensor-plugin.webxml custom-types.webxml database.webxml datastreamformat.webxml -datavisualization-examples.webxml dbus-changes-qt6.webxml determining-the-default-sensor-for-a-type.webxml dialogs.webxml @@ -117,7 +116,6 @@ qml-tutorial1.webxml qml-tutorial2.webxml qml-tutorial3.webxml qmldiskcache.webxml -qmlexampletoggleswitch.webxml qmlreference.webxml qndeffilter-record.webxml qoutputrange.webxml @@ -133,8 +131,6 @@ qsggeometry-texturedpoint2d.webxml qsgmaterialshader-graphicspipelinestate.webxml qsgmaterialtype.webxml qstaticplugin.webxml -qt3d-advancedcustommaterial-example.webxml -qt3d-audio-visualizer-qml-example.webxml qt3d-basicshapes-cpp-example.webxml qt3d-changes-qt6.webxml qt3d-cpp.webxml @@ -142,15 +138,9 @@ qt3d-examples.webxml qt3d-multiviewport-example.webxml qt3d-overview.webxml qt3d-pbr-materials-example.webxml -qt3d-planets-qml-example.webxml qt3d-qml.webxml -qt3d-scene2d-example.webxml -qt3d-scene3d-example.webxml -qt3d-shadow-map-qml-example.webxml qt3d-simple-qml-example.webxml qt3d-simplecustommaterial-example.webxml -qt3d-wave-example.webxml -qt3d-widgets-scene3d-example.webxml qt3d-wireframe-example.webxml qt3drender-framegraph.webxml qt3drender-geometry.webxml @@ -162,7 +152,6 @@ qtbluetooth-changes-qt6.webxml qtbluetooth-le-overview.webxml qtbluetooth-lowenergyscanner-example.webxml qtbluetooth-overview.webxml -qtbluetooth-pingpong-example.webxml qtcborcommon.webxml qtcharts-barmodelmapper-example.webxml qtcharts-boxplotchart-example.webxml @@ -170,7 +159,6 @@ qtcharts-candlestickchart-example.webxml qtcharts-changes-qt6.webxml qtcharts-customchart-example.webxml qtcharts-datetimeaxis-example.webxml -qtcharts-donutchart-example.webxml qtcharts-examples.webxml qtcharts-horizontalbarchart-example.webxml qtcharts-horizontalpercentbarchart-example.webxml @@ -179,28 +167,20 @@ qtcharts-legendmarkers-example.webxml qtcharts-multiaxis-example.webxml qtcharts-openglseries-example.webxml qtcharts-overview.webxml -qtcharts-piechartcustomization-example.webxml -qtcharts-piechartdrilldown-example.webxml qtcharts-pointsselectionandmarkers-example.webxml qtcharts-polarchart-example.webxml qtcharts-qmlaxes-example.webxml -qtcharts-qmlchart-example.webxml qtcharts-qmlcustomizations-example.webxml qtcharts-qmlcustomlegend-example.webxml qtcharts-qmlf1legends-example.webxml qtcharts-qmloscilloscope-example.webxml qtcharts-qmlweather-example.webxml qtcharts-scatterchart-example.webxml -qtcharts-scatterinteractions-example.webxml qtcharts-selectedbar-example.webxml qtcharts-splinechart-example.webxml qtcharts-stackedbarchart-example.webxml -qtcharts-stackedbarchartdrilldown-example.webxml qtcharts-zoomlinechart-example.webxml qtconcurrent-imagescaling-example.webxml -qtconcurrent-map-example.webxml -qtconcurrent-progressdialog-example.webxml -qtconcurrent-runfunction-example.webxml qtconcurrent-wordcount-example.webxml qtconcurrentfilter.webxml qtconcurrentmap.webxml @@ -215,31 +195,6 @@ qtcore-threads-queuedcustomtype-example.webxml qtcore-threads-semaphores-example.webxml qtcore-threads-waitconditions-example.webxml qtcore-tools-contiguouscache-example.webxml -qtcore-tools-customtype-example.webxml -qtdatavis3d-custominput-example.webxml -qtdatavis3d-customitems-example.webxml -qtdatavis3d-customproxy-example.webxml -qtdatavis3d-draggableaxes-example.webxml -qtdatavis3d-itemmodel-example.webxml -qtdatavis3d-qml3doscilloscope-example.webxml -qtdatavis3d-qmlaxisdrag-example.webxml -qtdatavis3d-qmlaxisformatter-example.webxml -qtdatavis3d-qmlbars-example.webxml -qtdatavis3d-qmlcustominput-example.webxml -qtdatavis3d-qmllegend-example.webxml -qtdatavis3d-qmlmultigraph-example.webxml -qtdatavis3d-qmlscatter-example.webxml -qtdatavis3d-qmlspectrogram-example.webxml -qtdatavis3d-qmlsurface-example.webxml -qtdatavis3d-qmlsurfacelayers-example.webxml -qtdatavis3d-rotations-example.webxml -qtdatavis3d-scatter-example.webxml -qtdatavis3d-texturesurface-example.webxml -qtdatavis3d-volumetric-example.webxml -qtdatavisualization-data-handling.webxml -qtdatavisualization-interacting-with-data.webxml -qtdatavisualization-known-issues.webxml -qtdatavisualization-overview.webxml qtdbus-chat-example.webxml qtdbus-cmake-qt-add-dbus-adaptor.webxml qtdbus-cmake-qt-add-dbus-interface.webxml @@ -253,8 +208,6 @@ qtdesigner-components.webxml qtdesigner-containerextension-example.webxml qtdesigner-customwidgetplugin-example.webxml qtdesigner-manual.webxml -qtdesigner-worldtimeclockbuilder-example.webxml -qtdesigner-worldtimeclockplugin-example.webxml qtest-overview.webxml qtest-tutorial.webxml qtextedit-extraselection.webxml @@ -271,18 +224,8 @@ qtjavascript.webxml qtmath.webxml qtmultimedia-apple.webxml qtmultimedia-changes-qt6.webxml -qtmultimedia-multimedia-audiodevices-example.webxml -qtmultimedia-multimedia-audiorecorder-example.webxml -qtmultimedia-multimedia-declarative-camera-example.webxml -qtmultimedia-multimedia-spectrum-example.webxml -qtmultimedia-multimedia-video-mediaplayer-example.webxml -qtmultimedia-multimedia-video-qmlvideo-example.webxml -qtmultimedia-multimedia-video-recorder-example.webxml -qtmultimedia-multimediawidgets-videographicsitem-example.webxml -qtmultimedia-multimediawidgets-videowidget-example.webxml qtnetwork-broadcastreceiver-example.webxml qtnetwork-broadcastsender-example.webxml -qtnetwork-downloadmanager-example.webxml qtnetwork-http-example.webxml qtnetwork-multicastreceiver-example.webxml qtnetwork-multicastsender-example.webxml @@ -292,8 +235,6 @@ qtnetwork-securesocketclient-example.webxml qtnetwork-secureudpclient-example.webxml qtnetwork-secureudpserver-example.webxml qtnetwork-torrent-example.webxml -qtnetworkauth-changes-qt6.webxml -qtnetworkauth-twittertimeline-example.webxml qtnfc-annotatedurl-example.webxml qtnfc-changes-qt6.webxml qtnfc-ndefeditor-example.webxml @@ -306,7 +247,6 @@ qtplugin.webxml qtpositioning-android.webxml qtpositioning-changes-qt6.webxml qtpositioning-examples.webxml -qtpositioning-geoflickr-example.webxml qtpositioning-ios.webxml qtpositioning-logfilepositionsource-example.webxml qtpositioning-plugins.webxml @@ -327,20 +267,16 @@ qtqml-javascript-imports.webxml qtqml-javascript-qmlglobalobject.webxml qtqml-javascript-resources.webxml qtqml-javascript-topic.webxml -qtqml-networkaccessmanagerfactory-example.webxml qtqml-qml-i18n-example.webxml -qtqml-qmlextensionplugins-example.webxml qtqml-syntax-basics.webxml qtqml-syntax-directoryimports.webxml qtqml-syntax-imports.webxml qtqml-syntax-objectattributes.webxml qtqml-syntax-propertybinding.webxml -qtqml-tool-qmltc.webxml qtqml-tutorials-extending-qml-example.webxml qtqml-typesystem-basictypes.webxml qtqml-typesystem-objecttypes.webxml qtqml-typesystem-topic.webxml -qtqml-xmlhttprequest-example.webxml qtquick-animation-example.webxml qtquick-bestpractices.webxml qtquick-canvas-example.webxml @@ -350,14 +286,11 @@ qtquick-cppextensionpoints.webxml qtquick-customitems-dialcontrol-example.webxml qtquick-customitems-flipable-example.webxml qtquick-customitems-painteditem-example.webxml -qtquick-customitems-scrollbar-example.webxml -qtquick-customitems-tabwidget-example.webxml qtquick-draganddrop-example.webxml qtquick-effects-particles.webxml qtquick-effects-sprites.webxml qtquick-effects-topic.webxml qtquick-effects-transformations.webxml -qtquick-externaldraganddrop-example.webxml qtquick-imageelements-example.webxml qtquick-imageprovider-example.webxml qtquick-imageresponseprovider-example.webxml @@ -385,10 +318,8 @@ qtquick-positioning-righttoleft.webxml qtquick-positioning-topic.webxml qtquick-quick-accessibility-example.webxml qtquick-quickwidgets-quickwidget-example.webxml -qtquick-righttoleft-example.webxml qtquick-scenegraph-custommaterial-example.webxml qtquick-scenegraph-d3d11underqml-example.webxml -qtquick-scenegraph-fboitem-example.webxml qtquick-scenegraph-graph-example.webxml qtquick-scenegraph-materials.webxml qtquick-scenegraph-metaltextureimport-example.webxml @@ -406,12 +337,8 @@ qtquick-statesanimations-topic.webxml qtquick-tableview-gameoflife-example.webxml qtquick-tableview-pixelator-example.webxml qtquick-text-example.webxml -qtquick-text-validator.webxml -qtquick-threading-example.webxml -qtquick-threading-threadedlistmodel-example.webxml qtquick-tool-qmllint.webxml qtquick-tools-and-utilities.webxml -qtquick-touchinteraction-example.webxml qtquick-tutorials-dynamicview-dynamicview1-example.webxml qtquick-tutorials-dynamicview-dynamicview2-example.webxml qtquick-tutorials-dynamicview-dynamicview3-example.webxml @@ -436,38 +363,35 @@ qtquickcontrols-contactlist-example.webxml qtquickcontrols-eventcalendar-example.webxml qtquickcontrols-flatstyle-example.webxml qtquickcontrols-imagine-automotive-example.webxml -qtquickcontrols-imagine-musicplayer-example.webxml -qtquickcontrols-sidepanel-example.webxml -qtquickcontrols-swipetoremove-example.webxml qtquickcontrols-texteditor-example.webxml qtquickcontrols-wearable-example.webxml -qtquickcontrols2-basic.webxml -qtquickcontrols2-buttons.webxml -qtquickcontrols2-configuration.webxml -qtquickcontrols2-containers.webxml -qtquickcontrols2-customize.webxml -qtquickcontrols2-delegates.webxml -qtquickcontrols2-deployment.webxml -qtquickcontrols2-environment.webxml -qtquickcontrols2-examples.webxml -qtquickcontrols2-fileselectors.webxml -qtquickcontrols2-focus.webxml -qtquickcontrols2-fusion.webxml -qtquickcontrols2-gettingstarted.webxml -qtquickcontrols2-guidelines.webxml -qtquickcontrols2-icons.webxml -qtquickcontrols2-imagine.webxml -qtquickcontrols2-indicators.webxml -qtquickcontrols2-input.webxml -qtquickcontrols2-macos.webxml -qtquickcontrols2-material.webxml -qtquickcontrols2-menus.webxml -qtquickcontrols2-navigation.webxml -qtquickcontrols2-popups.webxml -qtquickcontrols2-separators.webxml -qtquickcontrols2-styles.webxml -qtquickcontrols2-universal.webxml -qtquickcontrols2-windows.webxml +qtquickcontrols-basic.webxml +qtquickcontrols-buttons.webxml +qtquickcontrols-configuration.webxml +qtquickcontrols-containers.webxml +qtquickcontrols-customize.webxml +qtquickcontrols-delegates.webxml +qtquickcontrols-deployment.webxml +qtquickcontrols-environment.webxml +qtquickcontrols-examples.webxml +qtquickcontrols-fileselectors.webxml +qtquickcontrols-focus.webxml +qtquickcontrols-fusion.webxml +qtquickcontrols-gettingstarted.webxml +qtquickcontrols-guidelines.webxml +qtquickcontrols-icons.webxml +qtquickcontrols-imagine.webxml +qtquickcontrols-indicators.webxml +qtquickcontrols-input.webxml +qtquickcontrols-macos.webxml +qtquickcontrols-material.webxml +qtquickcontrols-menus.webxml +qtquickcontrols-navigation.webxml +qtquickcontrols-popups.webxml +qtquickcontrols-separators.webxml +qtquickcontrols-styles.webxml +qtquickcontrols-universal.webxml +qtquickcontrols-windows.webxml qtquicklayouts-overview.webxml qtremoteobjects-cmake-qt-add-repc-merged.webxml qtremoteobjects-cmake-qt-add-repc-replicas.webxml @@ -479,7 +403,6 @@ qtremoteobjects-external-schemas.webxml qtremoteobjects-gettingstarted.webxml qtremoteobjects-interaction.webxml qtremoteobjects-node.webxml -qtremoteobjects-qmlmodelviewclient-example.webxml qtremoteobjects-registry.webxml qtremoteobjects-repc.webxml qtremoteobjects-replica.webxml @@ -487,21 +410,11 @@ qtremoteobjects-source.webxml qtremoteobjects-ssl-example.webxml qtremoteobjects-troubleshooting.webxml qtremoteobjects-websockets-example.webxml -qtscxml-calculator-qml-example.webxml -qtscxml-calculator-widgets-example.webxml qtscxml-changes-qt6.webxml qtscxml-cmake-qt-add-statecharts.webxml qtscxml-ftpclient-example.webxml qtscxml-instantiating-state-machines.webxml -qtscxml-invoke-dynamic-example.webxml -qtscxml-invoke-static-example.webxml -qtscxml-mediaplayer-qml-cppdatamodel-example.webxml -qtscxml-mediaplayer-qml-dynamic-example.webxml -qtscxml-mediaplayer-qml-static-example.webxml -qtscxml-mediaplayer-widgets-dynamic-example.webxml -qtscxml-mediaplayer-widgets-static-example.webxml qtscxml-overview.webxml -qtscxml-pinball-example.webxml qtscxml-scxml-compliance.webxml qtscxml-sudoku-example.webxml qtscxml-trafficlight-qml-dynamic-example.webxml @@ -509,24 +422,13 @@ qtscxml-trafficlight-qml-simple-example.webxml qtscxml-trafficlight-qml-static-example.webxml qtscxml-trafficlight-widgets-dynamic-example.webxml qtscxml-trafficlight-widgets-static-example.webxml -qtsensors-accelbubble-example.webxml qtsensors-changes-qt6.webxml qtsensors-cpp.webxml qtsensors-examples.webxml -qtsensors-grue-example.webxml -qtsensors-maze-example.webxml -qtsensors-qmlqtsensors-example.webxml -qtsensors-sensor-explorer-example.webxml qtsensors-sensorsshowcase-example.webxml qtserialport-blockingreceiver-example.webxml qtserialport-blockingsender-example.webxml -qtserialport-cenumerator-example.webxml qtserialport-changes-qt6.webxml -qtserialport-creaderasync-example.webxml -qtserialport-creadersync-example.webxml -qtserialport-cwriterasync-example.webxml -qtserialport-cwritersync-example.webxml -qtserialport-enumerator-example.webxml qtserialport-examples.webxml qtserialport-terminal-example.webxml qtsql-cachedtable-example.webxml @@ -538,9 +440,6 @@ qtsql-sqlbrowser-example.webxml qtsql-sqlwidgetmapper-example.webxml qtsql-tablemodel-example.webxml qtsvg-changes-qt6.webxml -qtsvg-richtext-textobject-example.webxml -qtsvg-svggenerator-example.webxml -qtsvg-svgviewer-example.webxml qttest-best-practices-qdoc.webxml qttestlib-tutorial1-example.webxml qttestlib-tutorial2-example.webxml @@ -548,7 +447,6 @@ qttestlib-tutorial3-example.webxml qttestlib-tutorial4-example.webxml qttestlib-tutorial5-example.webxml qttestlib-tutorial6.webxml -qtuitools-multipleinheritance-example.webxml qtuitools-textfinder-example.webxml qtwebchannel-changes-qt6.webxml qtwebchannel-chatclient-html-example.webxml @@ -560,21 +458,14 @@ qtwebengine-changes-qt6.webxml qtwebengine-features.webxml qtwebengine-overview.webxml qtwebengine-platform-notes.webxml -qtwebengine-webenginequick-customdialogs-example.webxml qtwebengine-webenginequick-lifecycle-example.webxml -qtwebengine-webenginequick-minimal-example.webxml -qtwebengine-webenginequick-recipebrowser-example.webxml -qtwebengine-webenginequick-webengineaction-example.webxml qtwebengine-webenginewidgets-contentmanipulation-example.webxml qtwebengine-webenginewidgets-cookiebrowser-example.webxml qtwebengine-webenginewidgets-html2pdf-example.webxml qtwebengine-webenginewidgets-maps-example.webxml -qtwebengine-webenginewidgets-minimal-example.webxml qtwebengine-webenginewidgets-printme-example.webxml qtwebengine-webenginewidgets-spellchecker-example.webxml -qtwebengine-webenginewidgets-stylesheetbrowser-example.webxml qtwebengine-webenginewidgets-videoplayer-example.webxml -qtwebengine-webenginewidgets-webui-example.webxml qtwebenginewidgets-qtwebkitportingguide.webxml qtwebsockets-changes-qt6.webxml qtwebsockets-echoclient-example.webxml @@ -586,57 +477,32 @@ qtwebsockets-simplechat-example.webxml qtwebsockets-sslechoclient-example.webxml qtwebsockets-sslechoserver-example.webxml qtwebsockets-testing.webxml -qtwidgets-draganddrop-fridgemagnets-example.webxml -qtwidgets-draganddrop-puzzle-example.webxml -qtwidgets-effects-fademessage-example.webxml qtwidgets-gestures-imagegestures-example.webxml qtwidgets-graphicsview-basicgraphicslayouts-example.webxml qtwidgets-graphicsview-chip-example.webxml -qtwidgets-graphicsview-embeddeddialogs-example.webxml -qtwidgets-graphicsview-flowlayout-example.webxml qtwidgets-graphicsview-simpleanchorlayout-example.webxml -qtwidgets-graphicsview-weatheranchorlayout-example.webxml -qtwidgets-itemviews-chart-example.webxml qtwidgets-itemviews-coloreditorfactory-example.webxml qtwidgets-itemviews-combowidgetmapper-example.webxml qtwidgets-itemviews-customsortfiltermodel-example.webxml -qtwidgets-itemviews-dirview-example.webxml qtwidgets-itemviews-frozencolumn-example.webxml -qtwidgets-itemviews-interview-example.webxml -qtwidgets-itemviews-pixelator-example.webxml -qtwidgets-itemviews-puzzle-example.webxml -qtwidgets-itemviews-simpledommodel-example.webxml qtwidgets-itemviews-simpletreemodel-example.webxml -qtwidgets-itemviews-simplewidgetmapper-example.webxml -qtwidgets-mainwindows-mainwindow-example.webxml qtwidgets-mainwindows-menus-example.webxml -qtwidgets-mainwindows-sdi-example.webxml qtwidgets-painting-affine-example.webxml qtwidgets-painting-composition-example.webxml qtwidgets-painting-deform-example.webxml -qtwidgets-painting-fontsampler-example.webxml qtwidgets-painting-gradients-example.webxml qtwidgets-painting-imagecomposition-example.webxml qtwidgets-painting-painterpaths-example.webxml qtwidgets-painting-pathstroke-example.webxml qtwidgets-painting-transformations-example.webxml -qtwidgets-richtext-calendar-example.webxml qtwidgets-tools-completer-example.webxml qtwidgets-tools-customcompleter-example.webxml qtwidgets-tools-echoplugin-example.webxml -qtwidgets-tools-i18n-example.webxml -qtwidgets-tools-plugandpaint-app-example.webxml -qtwidgets-tools-plugandpaint-plugins-basictools-example.webxml -qtwidgets-tools-plugandpaint-plugins-extrafilters-example.webxml qtwidgets-tools-settingseditor-example.webxml qtwidgets-tools-styleplugin-example.webxml qtwidgets-tools-treemodelcompleter-example.webxml -qtwidgets-tools-undo-example.webxml qtwidgets-tools-undoframework-example.webxml -qtwidgets-touch-dials-example.webxml -qtwidgets-touch-fingerpaint-example.webxml qtwidgets-touch-knobs-example.webxml -qtwidgets-touch-pinchzoom-example.webxml qtwidgets-tutorials-notepad-example.webxml qtwidgets-tutorials-widgets-childwidget-example.webxml qtwidgets-tutorials-widgets-nestedlayouts-example.webxml @@ -645,27 +511,14 @@ qtwidgets-tutorials-widgets-windowlayout-example.webxml qtwidgets-widgets-analogclock-example.webxml qtwidgets-widgets-calculator-example.webxml qtwidgets-widgets-calendarwidget-example.webxml -qtwidgets-widgets-digitalclock-example.webxml -qtwidgets-widgets-elidedlabel-example.webxml qtwidgets-widgets-groupbox-example.webxml -qtwidgets-widgets-icons-example.webxml -qtwidgets-widgets-imageviewer-example.webxml qtwidgets-widgets-lineedits-example.webxml -qtwidgets-widgets-mousebuttons-example.webxml -qtwidgets-widgets-movie-example.webxml qtwidgets-widgets-scribble-example.webxml qtwidgets-widgets-shapedclock-example.webxml qtwidgets-widgets-sliders-example.webxml qtwidgets-widgets-spinboxes-example.webxml -qtwidgets-widgets-styles-example.webxml -qtwidgets-widgets-stylesheet-example.webxml qtwidgets-widgets-tablet-example.webxml -qtwidgets-widgets-tooltips-example.webxml -qtwidgets-widgets-validators-example.webxml -qtwidgets-widgets-wiggly-example.webxml qtwidgets-widgets-windowflags-example.webxml -qtxml-streambookmarks-example.webxml -qtxml-xmlstreamlint-example.webxml quick-changes-qt6.webxml qwebenginecookiestore-filterrequest.webxml qwidget-styling.webxml @@ -705,9 +558,7 @@ stylesheet-syntax.webxml stylesheet.webxml svgrendering.webxml testlib-changes-qt6.webxml -textedit-example.webxml timers.webxml -tutorials-addressbook.webxml usingadaptors.webxml videooverview.webxml webengine-examples.webxml @@ -725,7 +576,6 @@ xml-tools.webxml # qtdoc repository accessible.webxml appicon.webxml -connectivity.webxml create-your-first-applications.webxml deployment.webxml desktop-integration.webxml @@ -733,7 +583,6 @@ exceptionsafety.webxml explore-qt.webxml get-and-install-qt.webxml gettingstarted.webxml -gettingstartedqml.webxml highdpi.webxml install-qt-design-studio.webxml ipc.webxml @@ -759,7 +608,6 @@ qundo.webxml rcc.webxml restoring-geometry.webxml scalability.webxml -scripting.webxml session.webxml sharedlibrary.webxml solutions-for-application-development.webxml @@ -773,5 +621,4 @@ topics-data-io.webxml topics-ui.webxml uic.webxml unicode.webxml -unix-signal-handlers.webxml wayland-and-qt.webxml diff --git a/sources/pyside6/doc/commercial/index.rst b/sources/pyside6/doc/commercial/index.rst index 6c4266a1..33c88cfe 100644 --- a/sources/pyside6/doc/commercial/index.rst +++ b/sources/pyside6/doc/commercial/index.rst @@ -1,7 +1,7 @@ .. _commercial-page: -Commercial Distribution -======================= +Commercial Use +============== |project| follows the same licensing that Qt has, which means that there are two distributions, the Community Edition (LGPLv3/GPLv3) and a Commercial Edition. For @@ -19,11 +19,14 @@ The only difference is that the ADP license **does not** include the extra ``Qt OPC UA``, ``Qt MQTT`` and ``Qt CoAP`` modules, which are distributed in a special Python wheel. -All commercial licenses include the 5.15.x and 6.2.x LTS releases. +|project| follows the same approach as Qt, meaning that commercial +users will have access to both our commercial packages for any +given version, or the special commercial LTS releases. Commercial users **should not** install the Community Edition distribution via ``pip install pyside6`` to avoid licensing problems, and should refer to the -packages provided in the `Qt Account`_. +packages that can be acquired from the `Qt Account`_, the Qt Installer, or +via the `qtpip` tool. Installation ------------ diff --git a/sources/pyside6/doc/conf.py.in b/sources/pyside6/doc/conf.py.in index 093cede5..45defe9e 100644 --- a/sources/pyside6/doc/conf.py.in +++ b/sources/pyside6/doc/conf.py.in @@ -30,7 +30,7 @@ extensions = ['sphinx.ext.autodoc', 'sphinx.ext.doctest', 'sphinx.ext.ifconfig', 'sphinx.ext.coverage', 'sphinx.ext.intersphinx', 'sphinx.ext.todo', 'sphinx.ext.graphviz', 'inheritance_diagram', 'pysideinclude', 'sphinx.ext.viewcode', - 'sphinx_design', 'sphinx_copybutton', 'myst_parser'] + 'sphinx_design', 'sphinx_copybutton', 'myst_parser', 'sphinx_tags',] myst_enable_extensions = [ "amsmath", @@ -77,7 +77,7 @@ master_doc = 'index' # General information about the project. project = u'PySide' -copyright = u'2021 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 (https://www.gnu.org/licenses/fdl.html) as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.' +copyright = u'2024 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 (https://www.gnu.org/licenses/fdl.html) as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. @@ -138,6 +138,11 @@ html_theme = 'furo' # further. For a list of options available for each theme, see the # documentation. html_theme_options = { + # FIXME: This option is currently enable because on the 'requirements-doc.txt' + # We are using a fork of the theme, to include this new option. + # This needs to be removed once the functionality is either upstreamed, + # or a similar option is provided. + "collapse_navbar": True, "dark_css_variables": { "color-brand-primary": "#2cde85", "color-brand-content": "#2cde85", @@ -170,7 +175,7 @@ html_logo = "@CMAKE_CURRENT_SOURCE_DIR@/_static/qtforpython.png" # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. -#html_favicon = None +html_favicon = "_static/qtforpython.ico" # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, @@ -229,3 +234,10 @@ if @SKIP_SPHINX_WARNINGS@: # -- Options for qthelp output --------------------------------------------------- qthelp_theme = 'pysidedocs_qthelp' + +# for example tagging based on supported platforms +tags_create_tags = True +tags_create_badges = True +tags_badge_colors = { + "Android": "info", +} diff --git a/sources/pyside6/doc/contents.rst b/sources/pyside6/doc/contents.rst index 0208cae8..04203a53 100644 --- a/sources/pyside6/doc/contents.rst +++ b/sources/pyside6/doc/contents.rst @@ -4,6 +4,7 @@ :maxdepth: 2 quickstart.rst + commercial/index.rst gettingstarted/index.rst api.rst tutorials/index.rst @@ -12,7 +13,6 @@ deployment/index.rst considerations.rst developer/index.rst - commercial/index.rst .. Intersphinx references in toctrees is not supported diff --git a/sources/pyside6/doc/developer/adapt_qt.rst b/sources/pyside6/doc/developer/adapt_qt.rst new file mode 100644 index 00000000..f8582f69 --- /dev/null +++ b/sources/pyside6/doc/developer/adapt_qt.rst @@ -0,0 +1,43 @@ +.. _developer-adapt-qt: + +Adapt to new Qt versions +======================== + +The dev branch of PySide is switched to a new Qt minor version +after its API review is finished and the API is stable. + +Until that happens, a patch should be continuously developed +to adapt to this version. + +The `new classes page `_ +is a good source of information for new API. + +New classes and should be added to the type system file (using +a ``since`` attribute) and ``CMakeList.txt`` file of the respective module. + +Should the class not be available on all platforms, the respective +``QT_CONFIG`` macro needs to be specified in the type system file and +feature checks need to be added to ``CMakeList.txt`` (see for example +``QPermission``). + +The process consists of running a build and evaluating the log file. +The script +`shiboken2tasks.py `_ +from the Qt Creator repository can be used to convert the shiboken warnings +into a `task file `_ +for display in the build issues pane of Qt Creator. + +Warnings about new enumerations will be shown there; they should be added +to type system file using a ``since`` attribute. + +Warnings about not finding a function signature for modification +also need to be handled; mostly this is a sign of a function parameter +being changed from ``int`` to ``qsizetype`` or similar. + +If the build succeeds, a test run should be done. + +The Qt source code should be checked for new overloads +(indicated by ``QT6_DECL_NEW_OVERLOAD_TAIL`` starting from 6.7). +The resolution needs to be decided for each individual case, +mostly by removing old functions and using ```` +to declare new API. diff --git a/sources/pyside6/doc/developer/add_module.rst b/sources/pyside6/doc/developer/add_module.rst index 321a3472..f75aa548 100644 --- a/sources/pyside6/doc/developer/add_module.rst +++ b/sources/pyside6/doc/developer/add_module.rst @@ -44,6 +44,15 @@ Add bindings of using ``#include `` since module include paths are not passed in PySide. +Distribution +------------ + +- Determine to which wheel the module belongs according to + `Qt Modules `_. +- Add the module to ``build_scripts/wheel_files.py`` for use by + ``create_wheels.py``. +- Add the module to one of the ``README.pyside6_*.md`` files. + Add documentation ----------------- diff --git a/sources/pyside6/doc/developer/add_port_example.rst b/sources/pyside6/doc/developer/add_port_example.rst index fdb400ec..b99641f4 100644 --- a/sources/pyside6/doc/developer/add_port_example.rst +++ b/sources/pyside6/doc/developer/add_port_example.rst @@ -10,10 +10,13 @@ You can either design an example from scratch or inspired in another application, or simply you can port an existing Qt example that does not have a Python counterpart. -For both cases, we recommend you to use tools like -`flake8 `_ -(or `ruff `_) to detect issues with your code. +Example code should be free of `flake8 `_ +warnings; this is enforced by a bot. A configuration file is provided +at the root of the repository. Offending lines can be excluded by a +``noqa`` directive if there is a good reason to do so. + Keep in mind we do allow 100 columns for line length. + Additionally, please use `isort `_ to keep the imports ordered and consistent with other examples. @@ -21,7 +24,7 @@ For example: .. code-block:: bash - $ flake8 --ignore=E266 your_file.py + $ flake8 --config pyside-setup/.flake8 your_file.py $ isort your_file.py diff --git a/sources/pyside6/doc/developer/documentation.rst b/sources/pyside6/doc/developer/documentation.rst index 1ebfc18c..517bd46f 100644 --- a/sources/pyside6/doc/developer/documentation.rst +++ b/sources/pyside6/doc/developer/documentation.rst @@ -69,4 +69,5 @@ There are 2 scripts used for determining the inheritance: * ``json_inheritance.py`` (env var ``INHERITANCE_FILE``) reads a inheritance.json file containing the class hierarchy generated by shiboken's doc generator. + * ``import_inheritance.py`` actually tries to import the class (legacy) diff --git a/sources/pyside6/doc/developer/feature-motivation.rst b/sources/pyside6/doc/developer/feature-motivation.rst index ea0cede5..1509ea72 100644 --- a/sources/pyside6/doc/developer/feature-motivation.rst +++ b/sources/pyside6/doc/developer/feature-motivation.rst @@ -286,7 +286,7 @@ When using ``__feature__`` often with common IDEs, you may want to provide a feature-aware version of ``.pyi`` files to get a correct display. The simplest way to change them all in-place is the command: -.. code-block:: python +.. code-block:: bash pyside6-genpyi all --feature snake_case true_property diff --git a/sources/pyside6/doc/developer/index.rst b/sources/pyside6/doc/developer/index.rst index d15b36bc..92c84259 100644 --- a/sources/pyside6/doc/developer/index.rst +++ b/sources/pyside6/doc/developer/index.rst @@ -17,6 +17,7 @@ Development Topics add_port_example.rst add_tool.rst documentation.rst + adapt_qt.rst extras.rst Implementation details diff --git a/sources/pyside6/doc/developer/signature_doc.rst b/sources/pyside6/doc/developer/signature_doc.rst index c81e9a55..a6c703da 100644 --- a/sources/pyside6/doc/developer/signature_doc.rst +++ b/sources/pyside6/doc/developer/signature_doc.rst @@ -318,7 +318,7 @@ A shortcut for this command is ``pyside6-genpyi``. A useful command to change all .pyi files to use all features is -.. code-block:: python +.. code-block:: bash pyside6-genpyi all --feature snake_case true_property diff --git a/sources/pyside6/doc/extras/QtBluetooth.rst b/sources/pyside6/doc/extras/QtBluetooth.rst index dda05f20..96589675 100644 --- a/sources/pyside6/doc/extras/QtBluetooth.rst +++ b/sources/pyside6/doc/extras/QtBluetooth.rst @@ -56,7 +56,7 @@ the environment variable ``QT_EVENT_DISPATCHER_CORE_FOUNDATION=1`` to circumvent this issue. Applications that don't use Classic Bluetooth will find a subset of -`QtBluetooth `_ is available, +`QtBluetooth`_ is available, as CoreBluetooth (Bluetooth LE) do not require either of :class:`QApplication` or :class:`QGuiApplication` . @@ -71,32 +71,32 @@ Guides Logging Categories ^^^^^^^^^^^^^^^^^^ -The `QtBluetooth `_ module +The `QtBluetooth`_ module exports the following :class:`logging categories<~.Configuring Categories>` : - +--------------------+--------------------------------------------------------------------------------------------------------------+ - |Logging Category |Description | - +--------------------+--------------------------------------------------------------------------------------------------------------+ - |qt.bluetooth |Enables logging of cross platform code path in `QtBluetooth `_| - +--------------------+--------------------------------------------------------------------------------------------------------------+ - |qt.bluetooth.android|Enables logging of the Android implementation | - +--------------------+--------------------------------------------------------------------------------------------------------------+ - |qt.bluetooth.bluez |Enables logging of the BLuez/Linux implementation | - +--------------------+--------------------------------------------------------------------------------------------------------------+ - |qt.bluetooth.ios |Enables logging of the iOS implementation | - +--------------------+--------------------------------------------------------------------------------------------------------------+ - |qt.bluetooth.osx |Enables logging of the macOS implementation | - +--------------------+--------------------------------------------------------------------------------------------------------------+ - |qt.bluetooth.windows|Enables logging of the Qt for Windows implementation | - +--------------------+--------------------------------------------------------------------------------------------------------------+ + +--------------------+-------------------------------------------------------------+ + |Logging Category |Description | + +--------------------+-------------------------------------------------------------+ + |qt.bluetooth |Enables logging of cross platform code path in `QtBluetooth`_| + +--------------------+-------------------------------------------------------------+ + |qt.bluetooth.android|Enables logging of the Android implementation | + +--------------------+-------------------------------------------------------------+ + |qt.bluetooth.bluez |Enables logging of the BLuez/Linux implementation | + +--------------------+-------------------------------------------------------------+ + |qt.bluetooth.ios |Enables logging of the iOS implementation | + +--------------------+-------------------------------------------------------------+ + |qt.bluetooth.osx |Enables logging of the macOS implementation | + +--------------------+-------------------------------------------------------------+ + |qt.bluetooth.windows|Enables logging of the Qt for Windows implementation | + +--------------------+-------------------------------------------------------------+ Logging categories can be used to enable additional warning and debug output -for `QtBluetooth `_ . More -detailed information about logging can be found in +for `QtBluetooth`_ . More detailed information about logging can be found in :class:`QLoggingCategory<~.QLoggingCategory>` . A quick way to enable all -`QtBluetooth `_ logging is to -add the following line to the ``main()`` function: +`QtBluetooth`_ logging is to add the following line to the ``main()`` +function:: - :: + QLoggingCategory.setFilterRules("qt.bluetooth* = true") - QLoggingCategory.setFilterRules("qt.bluetooth* = true") + +.. _QtBluetooth: https://doc.qt.io/qt-6/qtbluetooth-module.html diff --git a/sources/pyside6/doc/extras/QtCore.ClassInfo.rst b/sources/pyside6/doc/extras/QtCore.ClassInfo.rst index 0cb6d32e..aa080aa0 100644 --- a/sources/pyside6/doc/extras/QtCore.ClassInfo.rst +++ b/sources/pyside6/doc/extras/QtCore.ClassInfo.rst @@ -22,7 +22,7 @@ value and enabling special characters in the key. Example ------- -:: +.. code-block:: python # Recommended syntax @ClassInfo(Author='PySide Team', URL='http://www.pyside.org') diff --git a/sources/pyside6/doc/extras/QtCore.Property.rst b/sources/pyside6/doc/extras/QtCore.Property.rst index f6f7bd4e..64567c4b 100644 --- a/sources/pyside6/doc/extras/QtCore.Property.rst +++ b/sources/pyside6/doc/extras/QtCore.Property.rst @@ -16,7 +16,7 @@ They are equivalent to the ``Q_PROPERTY`` macro in the `Qt Docs`_. Here is an example that illustrates how to use this function: -.. code-block:: +.. code-block:: python :linenos: from PySide6.QtCore import QObject, Property @@ -40,20 +40,20 @@ function: The full options for ``QtCore.Property`` can be found with ``QtCore.Property.__doc__``: -.. code-block:: - - Property(self, type: type, - fget: Optional[Callable] = None, - fset: Optional[Callable] = None, - freset: Optional[Callable] = None, - fdel: Optional[Callable] = None, - doc: str = '', - notify: Optional[Callable] = None, - designable: bool = True, - scriptable: bool = True, - stored: bool = True, user: bool = False, - constant: bool = False, - final: bool = False) -> PySide6.QtCore.Property +.. code-block:: python + + Property(self, type: type, + fget: Optional[Callable] = None, + fset: Optional[Callable] = None, + freset: Optional[Callable] = None, + fdel: Optional[Callable] = None, + doc: str = '', + notify: Optional[Callable] = None, + designable: bool = True, + scriptable: bool = True, + stored: bool = True, user: bool = False, + constant: bool = False, + final: bool = False) -> PySide6.QtCore.Property Normally, only ``type``, ``fget``and ``fset`` are used. @@ -68,16 +68,16 @@ requires a ``type`` parameter. In the above example, the following lines would be equivalent properties: -.. code-block:: +.. code-block:: python - pp = QtCore.Property(int, readPP, setPP) # PySide version - pp = property(readPP, setPP) # Python version + pp = QtCore.Property(int, readPP, setPP) # PySide version + pp = property(readPP, setPP) # Python version As you know from the `Python Docs`_, ``Python`` allows to break the property creation into multiple steps, using the decorator syntax. We can do this in ``PySide`` as well: -.. code-block:: +.. code-block:: python :linenos: from PySide6.QtCore import QObject, Property @@ -110,7 +110,7 @@ If you are using properties of your objects in QML expressions, QML requires that the property changes are notified. Here is an example illustrating how to do this: -.. code-block:: +.. code-block:: python :linenos: from PySide6.QtCore import QObject, Signal, Property diff --git a/sources/pyside6/doc/extras/QtCore.Signal.rst b/sources/pyside6/doc/extras/QtCore.Signal.rst index b50c3414..43dd4921 100644 --- a/sources/pyside6/doc/extras/QtCore.Signal.rst +++ b/sources/pyside6/doc/extras/QtCore.Signal.rst @@ -1,8 +1,8 @@ .. currentmodule:: PySide6.QtCore .. _Signal: -Signal -****** +Qt Signal +********* Synopsis -------- @@ -10,19 +10,19 @@ Synopsis Functions ^^^^^^^^^ -+---------------------------------------------------------------------------------------------+ -|def :meth:`connect` (receiver) | -+---------------------------------------------------------------------------------------------+ -|def :meth:`disconnect` (receiver) | -+---------------------------------------------------------------------------------------------+ -|def :meth:`emit` (\*args) | -+---------------------------------------------------------------------------------------------+ ++-----------------------------------------------------+ +|def :meth:`connect` (receiver) | ++-----------------------------------------------------+ +|def :meth:`disconnect` (receiver) | ++-----------------------------------------------------+ +|def :meth:`emit` (\*args) | ++-----------------------------------------------------+ Detailed Description -------------------- - The :class:`~.Signal` class provides a way to declare and connect Qt - signals in a pythonic way. +The :class:`~.Signal` class provides a way to declare and connect Qt +signals in a pythonic way. .. class:: PySide6.QtCore.Signal([type1 [, type2...]] [, name="" [, arguments=[]]]) diff --git a/sources/pyside6/doc/extras/QtCore.Slot.rst b/sources/pyside6/doc/extras/QtCore.Slot.rst index c6210436..06646e33 100644 --- a/sources/pyside6/doc/extras/QtCore.Slot.rst +++ b/sources/pyside6/doc/extras/QtCore.Slot.rst @@ -1,8 +1,8 @@ .. currentmodule:: PySide6.QtCore .. _Slot: -Slot -**** +Qt Slots +******** Detailed Description -------------------- diff --git a/sources/pyside6/doc/extras/QtDesigner.QPyDesignerContainerExtension.rst b/sources/pyside6/doc/extras/QtDesigner.QPyDesignerContainerExtension.rst index 1baa9e36..5280e5e2 100644 --- a/sources/pyside6/doc/extras/QtDesigner.QPyDesignerContainerExtension.rst +++ b/sources/pyside6/doc/extras/QtDesigner.QPyDesignerContainerExtension.rst @@ -5,6 +5,8 @@ QPyDesignerContainerExtension ***************************** QPyDesignerContainerExtension is the base class for implementing -`QDesignerContainerExtension `_ +`QDesignerContainerExtension class`_ for a Qt Designer custom widget plugin in Python. It provides the required inheritance from **QObject**. + +.. _QDesignerContainerExtension class: https://doc.qt.io/qt-6/qdesignercontainerextension.html diff --git a/sources/pyside6/doc/extras/QtDesigner.QPyDesignerCustomWidgetCollection.rst b/sources/pyside6/doc/extras/QtDesigner.QPyDesignerCustomWidgetCollection.rst index 00260860..71ef6cd6 100644 --- a/sources/pyside6/doc/extras/QtDesigner.QPyDesignerCustomWidgetCollection.rst +++ b/sources/pyside6/doc/extras/QtDesigner.QPyDesignerCustomWidgetCollection.rst @@ -19,20 +19,20 @@ Functions Detailed Description -------------------- - The :class:`~.QPyDesignerCustomWidgetCollection` implements - `QDesignerCustomWidgetCollectionInterface `_ - and provides static helper functions for registering custom widgets by - type or by implementing - `QDesignerCustomWidgetInterface `_ . +The :class:`~.QPyDesignerCustomWidgetCollection` implements +`QDesignerCustomWidgetCollectionInterface `_ +and provides static helper functions for registering custom widgets by +type or by implementing +`QDesignerCustomWidgetInterface`_ . - The usage is explained in :ref:`designer_custom_widgets`. +The usage is explained in :ref:`designer_custom_widgets`. .. py:staticmethod:: QPyDesignerCustomWidgetCollection.registerCustomWidget(type[, xml=""[, tool_tip=""[, icon=""[, group=""[container=False]]]]]) Registers an instance of a Python-implemented QWidget by type with Qt Designer. The optional keyword arguments correspond to the getters of - `QDesignerCustomWidgetInterface `_ : + `QDesignerCustomWidgetInterface`_ : :param str xml: A snippet of XML code in ``.ui`` format that specifies how the widget is created and sets initial property values. :param str tool_tip: Tool tip to be shown in the widget box. @@ -46,7 +46,9 @@ Detailed Description .. py:staticmethod:: QPyDesignerCustomWidgetCollection.addCustomWidget(custom_widget) Adds a custom widget (implementation of - `QDesignerCustomWidgetInterface `_) + `QDesignerCustomWidgetInterface`_) with Qt Designer. :param QDesignerCustomWidgetInterface custom_widget: Custom widget instance + +.. _QDesignerCustomWidgetInterface: https://doc.qt.io/qt-6/qdesignercustomwidgetinterface.html diff --git a/sources/pyside6/doc/extras/QtDesigner.QPyDesignerMemberSheetExtension.rst b/sources/pyside6/doc/extras/QtDesigner.QPyDesignerMemberSheetExtension.rst index c52aafdf..997da148 100644 --- a/sources/pyside6/doc/extras/QtDesigner.QPyDesignerMemberSheetExtension.rst +++ b/sources/pyside6/doc/extras/QtDesigner.QPyDesignerMemberSheetExtension.rst @@ -5,6 +5,8 @@ QPyDesignerMemberSheetExtension ******************************* QPyDesignerMemberSheetExtension is the base class for implementing -`QDesignerMemberSheetExtension `_ +`QDesignerMemberSheetExtension class`_ for a Qt Designer custom widget plugin in Python. It provides the required inheritance from **QObject**. + +.. _QDesignerMemberSheetExtension class: https://doc.qt.io/qt-6/qdesignermembersheetextension.html diff --git a/sources/pyside6/doc/extras/QtDesigner.QPyDesignerTaskMenuExtension.rst b/sources/pyside6/doc/extras/QtDesigner.QPyDesignerTaskMenuExtension.rst index 004c9469..84d19c54 100644 --- a/sources/pyside6/doc/extras/QtDesigner.QPyDesignerTaskMenuExtension.rst +++ b/sources/pyside6/doc/extras/QtDesigner.QPyDesignerTaskMenuExtension.rst @@ -5,6 +5,8 @@ QPyDesignerTaskMenuExtension **************************** QPyDesignerTaskMenuExtension is the base class for implementing -`QDesignerTaskMenuExtension `_ +`QDesignerTaskMenuExtension class`_ for a Qt Designer custom widget plugin in Python. It provides the required inheritance from **QObject**. + +.. _QDesignerTaskMenuExtension class: https://doc.qt.io/qt-6/qdesignertaskmenuextension.html diff --git a/sources/pyside6/doc/extras/QtMultimedia.rst b/sources/pyside6/doc/extras/QtMultimedia.rst index f495ddc4..527c2427 100644 --- a/sources/pyside6/doc/extras/QtMultimedia.rst +++ b/sources/pyside6/doc/extras/QtMultimedia.rst @@ -29,7 +29,7 @@ directive: import PySide6.QtMultimedia -The module also provides `QML types `_ . +The module also provides QML `types `_ . Overviews and Important Topics ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -46,27 +46,38 @@ QML Types The following table outlines some important QML types. - +-------------------------------------------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ - |Type |Description | - +-------------------------------------------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ - |`MediaPlayer `_ |Add audio/video playback functionality to a scene. | - +-------------------------------------------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ - |`CaptureSession `_|Create a session for capturing audio/video. | - +-------------------------------------------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ - |`Camera `_ |Access a camera connected to the system. | - +-------------------------------------------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ - |`AudioInput `_ |Access an audio input (microphone) connected to the system. | - +-------------------------------------------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ - |`AudioOutput `_ |Access an audio output (speaker, headphone) connected to the system. | - +-------------------------------------------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ - |`VideoOutput `_ |Display video content. | - +-------------------------------------------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ - |`MediaRecorder `_ |Record audio/video from the `CaptureSession `_ . | - +-------------------------------------------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ - |`ImageCapture `_ |Capture still images from the Camera. | - +-------------------------------------------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ - |`Video `_ |Add Video playback functionality to a scene. Uses `MediaPlayer `_ and `VideoOutput `_ types to provide video playback functionality.| - +-------------------------------------------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ + +------------------+---------------------------------------------------------------------+ + |Type |Description | + +------------------+---------------------------------------------------------------------+ + |`MediaPlayer`_ |Add audio/video playback functionality to a scene. | + +------------------+---------------------------------------------------------------------+ + |`CaptureSession`_ |Create a session for capturing audio/video. | + +------------------+---------------------------------------------------------------------+ + |`Camera`_ |Access a camera connected to the system. | + +------------------+---------------------------------------------------------------------+ + |`AudioInput`_ |Access an audio input (microphone) connected to the system. | + +------------------+---------------------------------------------------------------------+ + |`AudioOutput`_ |Access an audio output (speaker, headphone) connected to the system. | + +------------------+---------------------------------------------------------------------+ + |`VideoOutput`_ |Display video content. | + +------------------+---------------------------------------------------------------------+ + |`MediaRecorder`_ |Record audio/video from the `CaptureSession`_ . | + +------------------+---------------------------------------------------------------------+ + |`ImageCapture`_ |Capture still images from the Camera. | + +------------------+---------------------------------------------------------------------+ + |`Video`_ |Add Video playback functionality to a scene. Uses `MediaPlayer`_ and | + | |`VideoOutput`_ types to provide video playback functionality. | + +------------------+---------------------------------------------------------------------+ + +.. _MediaPlayer: https://doc.qt.io/qt-6/qml-qtmultimedia-mediaplayer.html +.. _CaptureSession: https://doc.qt.io/qt-6/qml-qtmultimedia-capturesession.html +.. _Camera: https://doc.qt.io/qt-6/qml-qtmultimedia-camera.html +.. _AudioInput: https://doc.qt.io/qt-6/qml-qtmultimedia-audioinput.html +.. _AudioOutput: https://doc.qt.io/qt-6/qml-qtmultimedia-audiooutput.html +.. _VideoOutput: https://doc.qt.io/qt-6/qml-qtmultimedia-videooutput.html +.. _MediaRecorder: https://doc.qt.io/qt-6/qml-qtmultimedia-mediarecorder.html +.. _ImageCapture: https://doc.qt.io/qt-6/qml-qtmultimedia-imagecapture.html +.. _Video: https://doc.qt.io/qt-6/qml-qtmultimedia-video.html C++ Classes ^^^^^^^^^^^ diff --git a/sources/pyside6/doc/extras/QtQml.QPyQmlParserStatus.rst b/sources/pyside6/doc/extras/QtQml.QPyQmlParserStatus.rst index 58f609b0..f027885a 100644 --- a/sources/pyside6/doc/extras/QtQml.QPyQmlParserStatus.rst +++ b/sources/pyside6/doc/extras/QtQml.QPyQmlParserStatus.rst @@ -5,6 +5,8 @@ QPyQmlParserStatus ****************** QPyQmlParserStatus is the base class for implementing -`QQmlParserStatus `_ +`QQmlParserStatus class`_ . It provides the required inheritance from **QObject**. + +.. _QQmlParserStatus class: https://doc.qt.io/qt-6/qqmlparserstatus.html diff --git a/sources/pyside6/doc/extras/QtQml.QPyQmlPropertyValueSource.rst b/sources/pyside6/doc/extras/QtQml.QPyQmlPropertyValueSource.rst index 576b8baa..fea23882 100644 --- a/sources/pyside6/doc/extras/QtQml.QPyQmlPropertyValueSource.rst +++ b/sources/pyside6/doc/extras/QtQml.QPyQmlPropertyValueSource.rst @@ -5,6 +5,8 @@ QPyQmlPropertyValueSource ************************* QPyQmlPropertyValueSource is the base class for implementing -`QQmlPropertyValueSource `_ +`QQmlPropertyValueSource class`_ . It provides the required inheritance from **QObject**. + +.. _QQmlPropertyValueSource class: https://doc.qt.io/qt-6/qqmlpropertyvaluesource.html diff --git a/sources/pyside6/doc/extras/QtQml.QmlSingleton.rst b/sources/pyside6/doc/extras/QtQml.QmlSingleton.rst index d3d3bf4a..a2430389 100644 --- a/sources/pyside6/doc/extras/QtQml.QmlSingleton.rst +++ b/sources/pyside6/doc/extras/QtQml.QmlSingleton.rst @@ -1,8 +1,8 @@ .. currentmodule:: PySide6.QtQml .. _QmlSingleton: -QmlSingleton -************ +QmlSingleton decorator +********************** .. py:decorator:: QmlSingleton @@ -20,6 +20,6 @@ constructor. @QmlElement @QmlSingleton class ClassForQml(QObject): - # ... + ... .. note:: The order of the decorators matters; ``QmlSingleton`` needs to be preceded by ``QmlElement``. diff --git a/sources/pyside6/doc/extras/QtQml.QmlUncreatable.rst b/sources/pyside6/doc/extras/QtQml.QmlUncreatable.rst index 72fde741..46c7c49d 100644 --- a/sources/pyside6/doc/extras/QtQml.QmlUncreatable.rst +++ b/sources/pyside6/doc/extras/QtQml.QmlUncreatable.rst @@ -26,6 +26,6 @@ Passing None or no argument will cause a standard message to be used instead. @QmlElement @QmlUncreatable("BaseClassForQml is an abstract base class") class BaseClassForQml(QObject): - # ... + ... .. note:: The order of the decorators matters; ``QmlUncreatable`` needs to be preceded by ``QmlElement``. diff --git a/sources/pyside6/doc/extras/QtUiTools.loadUiType.rst b/sources/pyside6/doc/extras/QtUiTools.loadUiType.rst index 8a8c4bef..48879d26 100644 --- a/sources/pyside6/doc/extras/QtUiTools.loadUiType.rst +++ b/sources/pyside6/doc/extras/QtUiTools.loadUiType.rst @@ -1,5 +1,4 @@ .. currentmodule:: PySide6.QtUiTools -.. _loadUiType: loadUiType *********** diff --git a/sources/pyside6/doc/faq/typesoffiles.rst b/sources/pyside6/doc/faq/typesoffiles.rst index 71d38f80..d3bd4512 100644 --- a/sources/pyside6/doc/faq/typesoffiles.rst +++ b/sources/pyside6/doc/faq/typesoffiles.rst @@ -58,7 +58,6 @@ XML. Here is an extract of the beginning of a ``.ui`` file: MainWindow - ... The `pyside6-uic` tool generates Python code from these `.ui` files, which you can import from your main files, so it is not necessary diff --git a/sources/pyside6/doc/faq/whatisshiboken.rst b/sources/pyside6/doc/faq/whatisshiboken.rst index e867fadd..bf6d19ab 100644 --- a/sources/pyside6/doc/faq/whatisshiboken.rst +++ b/sources/pyside6/doc/faq/whatisshiboken.rst @@ -4,11 +4,9 @@ Binding Generation: What Is Shiboken? ===================================== When you install ``PySide6`` you might have notice that also ``Shiboken6`` -is installed as a dependency: +is installed as a dependency:: -.. code-block:: bash - - (env) [qt ~]$ pip install pyside6 + $ pip install pyside6 Collecting pyside6 Downloading PySide6-6.0.0-6.0.0-cp36.cp37.cp38.cp39-abi3-manylinux1_x86_64.whl (170.5 MB) |████████████████████████████████| 170.5 MB 42 kB/s diff --git a/sources/pyside6/doc/gettingstarted/linux.rst b/sources/pyside6/doc/gettingstarted/linux.rst index 111085ad..912105ef 100644 --- a/sources/pyside6/doc/gettingstarted/linux.rst +++ b/sources/pyside6/doc/gettingstarted/linux.rst @@ -86,7 +86,7 @@ Assumming that Qt is in PATH, for example, the configure step can be done with:: cmake -B /path/to/the/build/directory \ -S /path/to/the/pyside-setup \ -DCMAKE_INSTALL_PREFIX=/where/to/install \ - -DPYTHON_EXECUTABLE=/path/to/interpreter + -DPython_EXECUTABLE=/path/to/interpreter .. note:: You can add `-DFORCE_LIMITED_API=yes` in case you want to have a build which will be compatible with Python 3.7+. diff --git a/sources/pyside6/doc/gettingstarted/porting_from2.rst b/sources/pyside6/doc/gettingstarted/porting_from2.rst index 07f5b48c..6699f54e 100644 --- a/sources/pyside6/doc/gettingstarted/porting_from2.rst +++ b/sources/pyside6/doc/gettingstarted/porting_from2.rst @@ -36,14 +36,14 @@ import statements: .. code-block:: python - from PySide2.QtWidgets import QApplication... + from PySide2.QtWidgets import QApplication from PySide2 import QtCore needs to be changed to: .. code-block:: python - from PySide6.QtWidgets import QApplication... + from PySide6.QtWidgets import QApplication from PySide6 import QtCore diff --git a/sources/pyside6/doc/inheritance_graph.py b/sources/pyside6/doc/inheritance_graph.py index 2911b458..e45ebb17 100644 --- a/sources/pyside6/doc/inheritance_graph.py +++ b/sources/pyside6/doc/inheritance_graph.py @@ -16,6 +16,22 @@ python inheritance_graph.py PySide6.QtWidgets PySide6.QtWidgets.QWizard """ +def format_dict(d): + """Format the URL dict for error message.""" + result = '{' + n = 0 + for k, v in d.items(): + n += 1 + if n > 10: + result += "..." + break + if n > 1: + result += ", " + result += f'"{k}": "{v}"' + result += '}' + return result + + class InheritanceGraph(object): """ Given a list of classes, determines the set of classes that they inherit @@ -104,6 +120,10 @@ class InheritanceGraph(object): if url is not None: this_node_attrs['URL'] = f'"{url}"' this_node_attrs['target'] = '"_top"' # Browser target frame attribute (same page) + else: + urls_str = format_dict(urls) + print(f'inheritance_graph.py: No URL found for {name} ({fullname}) in {urls_str}.', + file=sys.stderr) attribute = self._format_node_attrs(this_node_attrs) res.append(f' "{name}" [{attribute}];\n') diff --git a/sources/pyside6/doc/modules.rst b/sources/pyside6/doc/modules.rst index 625d60a7..bffb49b7 100644 --- a/sources/pyside6/doc/modules.rst +++ b/sources/pyside6/doc/modules.rst @@ -224,3 +224,7 @@ Qt Modules Supported by Qt for Python .. grid-item-card:: :mod:`Qt3D Render ` Contains functionality to support 2D and 3D rendering using Qt 3D. + + .. grid-item-card:: :mod:`QtAsyncio ` + + Provides integration between asyncio and Qt's event loop. diff --git a/sources/pyside6/doc/qdoc_spawner.py.in b/sources/pyside6/doc/qdoc_spawner.py.in new file mode 100644 index 00000000..0223e50e --- /dev/null +++ b/sources/pyside6/doc/qdoc_spawner.py.in @@ -0,0 +1,99 @@ +# Copyright (C) 2024 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +import argparse +import subprocess +import os +import sys +import time +from multiprocessing import Pool, cpu_count +from pathlib import Path + + +def run_qdoc(file, qdoc_args, args): + env = os.environ + env["BUILDDIR"] = args.build_dir + env["QT_INSTALL_DOCS"] = args.qt_install_docs + env["QT_VERSION"] = args.qt_version + env["QT_VER"] = ".".join(args.qt_version.split(".")[:2]) + env["QT_VERSION_TAG"] = args.qt_version + + command = [ + args.qdoc_bin, + file, + *qdoc_args, + "-installdir", + args.doc_data_dir, + "-outputdir", + args.doc_data_dir, + ] + + start_time = time.time() + _ = subprocess.Popen(command, env=env, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + out, err = _.communicate() + returncode = _.wait() + duration = round(time.time() - start_time) + + if args.verbose or returncode != 0 or err: + cmd_str = " ".join(command) + print(f"> Output of: {cmd_str}") + if out: + print(out.decode("utf-8"), file=sys.stdout) + if err: + print(err.decode("utf-8"), file=sys.stderr) + print(f"> Finished: {file} {duration}s (exit code {returncode})") + + if returncode != 0: + raise Exception(f"Failing executing the command {command} ({returncode})") + + +def get_qdocconf_files(): + if not Path("pyside.qdocconf").exists(): + print("ERROR: the working dir doesn't include a 'pyside.qdocconf' file") + sys.exit(-1) + + # Generate the temporary qdocconf files + # This is necessary because using a file like 'pyside-qtcore.qtdocconf' + # will generate an error, because inside we call functions like 'include()' + files_single_exec = [] + files_prepare = [] + with open("pyside.qdocconf") as f: + for i in f.read().splitlines(): + _p = Path(i) + _name = f"_{_p.stem}.qdocconf" + with open(_name, "w", encoding="utf-8") as f: + f.write(i) + files_single_exec.append(_name) + files_prepare.append(i.strip()) + + return files_prepare, files_single_exec + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(prog="qdoc spawner") + parser.add_argument("--qt", dest="qt_version", action="store", required=True) + parser.add_argument("--doc-data-dir", dest="doc_data_dir", action="store", required=True) + parser.add_argument("--qdoc-binary", dest="qdoc_bin", action="store", required=True) + parser.add_argument("--build-dir", dest="build_dir", action="store", required=True) + parser.add_argument("--qt-install-docs", dest="qt_install_docs", action="store", required=True) + parser.add_argument("--parallel", dest="parallel", action="store", default="4") + parser.add_argument("--verbose", dest="verbose", action="store_true", default=False) + + args = parser.parse_args() + files_prepare, files_single_exec = get_qdocconf_files() + + parallel = args.parallel + if parallel == "auto": + parallel = cpu_count() + + try: + # mode: -prepare -no-link-errors + with Pool(int(parallel)) as p: + p.starmap(run_qdoc, [(str(f), ["-prepare", "-no-link-errors"], args) for f in files_prepare]) + + # mode: -single-exec + with Pool(int(parallel)) as p: + p.starmap(run_qdoc, [(str(f), ["-single-exec"], args) for f in files_single_exec]) + except Exception as e: + print(f"qdoc_spawner: error: {e}", file=sys.stderr) + sys.exit(-1) diff --git a/sources/pyside6/doc/quickstart.rst b/sources/pyside6/doc/quickstart.rst index e5b13cc4..7e1a210f 100644 --- a/sources/pyside6/doc/quickstart.rst +++ b/sources/pyside6/doc/quickstart.rst @@ -28,6 +28,8 @@ Before you can install |project|, first you must install the following software: Installation ------------ +.. note:: For a commercial installation, refer to :ref:`commercial-page`. + * **Creating and activating an environment** You can do this by running the following on a terminal: diff --git a/sources/pyside6/doc/tutorials/basictutorial/clickablebutton.rst b/sources/pyside6/doc/tutorials/basictutorial/clickablebutton.rst index 2221858b..c5464640 100644 --- a/sources/pyside6/doc/tutorials/basictutorial/clickablebutton.rst +++ b/sources/pyside6/doc/tutorials/basictutorial/clickablebutton.rst @@ -60,7 +60,8 @@ signal to the `say_hello()` function: button.clicked.connect(say_hello) Finally, we show the button and start the Qt main loop: -:: + +.. code-block:: python # Show the button button.show() @@ -68,9 +69,8 @@ Finally, we show the button and start the Qt main loop: app.exec() Here is the complete code for this example: -:: - #!/usr/bin/python +.. code-block:: python import sys from PySide6.QtWidgets import QApplication, QPushButton diff --git a/sources/pyside6/doc/tutorials/basictutorial/dialog.rst b/sources/pyside6/doc/tutorials/basictutorial/dialog.rst index e2512f9d..b7712672 100644 --- a/sources/pyside6/doc/tutorials/basictutorial/dialog.rst +++ b/sources/pyside6/doc/tutorials/basictutorial/dialog.rst @@ -105,7 +105,8 @@ Complete code ------------- Here is the complete code for this tutorial: -:: + +.. code-block:: python import sys from PySide6.QtWidgets import (QLineEdit, QPushButton, QApplication, diff --git a/sources/pyside6/doc/tutorials/basictutorial/translations.rst b/sources/pyside6/doc/tutorials/basictutorial/translations.rst index e4f06f43..6ca403ba 100644 --- a/sources/pyside6/doc/tutorials/basictutorial/translations.rst +++ b/sources/pyside6/doc/tutorials/basictutorial/translations.rst @@ -9,10 +9,10 @@ Translating Applications Qt Linguist ----------- -`Qt Linguist `_ and +`Qt Linguist`_ and its related tools can be used to provide translations for applications. -The ``examples/widgets/linguist`` example illustrates this. The example is +The :ref:`qt-linguist-example` example illustrates this. The example is very simple, it has a menu and shows a list of programming languages with multiselection. @@ -98,13 +98,15 @@ The example can then be run in German: LANG=de python main.py +.. _Qt Linguist: https://doc.qt.io/qt-6/qtlinguist-index.html + GNU gettext ----------- -The `GNU gettext `_ module +The `GNU gettext`_ module can be used to provide translations for applications. -The ``examples/widgets/gettext`` example illustrates this. The example is +The :ref:`gettext-example` example illustrates this. The example is very simple, it has a menu and shows a list of programming languages with multiselection. @@ -119,7 +121,7 @@ Those functions are defined at the top: .. code-block:: python import gettext - ... + # ... _ = None ngettext = None @@ -212,3 +214,5 @@ The example can then be run in German: .. code-block:: bash LANG=de python main.py + +.. _GNU gettext: https://docs.python.org/3/library/gettext.html diff --git a/sources/pyside6/doc/tutorials/basictutorial/treewidget.rst b/sources/pyside6/doc/tutorials/basictutorial/treewidget.rst index b286de50..f431cb5c 100644 --- a/sources/pyside6/doc/tutorials/basictutorial/treewidget.rst +++ b/sources/pyside6/doc/tutorials/basictutorial/treewidget.rst @@ -11,14 +11,14 @@ information in trees. You can also create a data model and display it using a further on. To know more about the Model/View architecture in Qt, refer to its `official documentation `_. -1. Import ``QTreeWidget`` and ``QTreeWidgetItem`` for this application: +#. Import ``QTreeWidget`` and ``QTreeWidgetItem`` for this application: .. code-block:: python import sys from PySide6.QtWidgets import QApplication, QTreeWidget, QTreeWidgetItem -2. Define a dictionary with project structures to display the information as a +#. Define a dictionary with project structures to display the information as a tree, with files belonging to each project: .. code-block:: python @@ -27,13 +27,13 @@ information in trees. You can also create a data model and display it using a "Project B": ["file_b.csv", "photo.jpg"], "Project C": []} -3. Initialize the ``QApplication`` singleton: +#. Initialize the ``QApplication`` singleton: .. code-block:: python app = QApplication() -4. Configure the ``QTreeWidget`` to have two columns, one for the item name, +#. Configure the ``QTreeWidget`` to have two columns, one for the item name, and the other for item type information of the files in the project directories. You can set the column name with the ``setHeaderLabels`` as described below: @@ -44,7 +44,7 @@ information in trees. You can also create a data model and display it using a tree.setColumnCount(2) tree.setHeaderLabels(["Name", "Type"]) -5. Iterate the data structure, create the ``QTreeWidgetItem`` elements, and add +#. Iterate the data structure, create the ``QTreeWidgetItem`` elements, and add the corresponding children to each parent. We also extract the extension name for only the files and add them into the second column. @@ -64,7 +64,7 @@ information in trees. You can also create a data model and display it using a tree.insertTopLevelItems(0, items) -7. Show the tree and execute the ``QApplication``. +#. Show the tree and execute the ``QApplication``. .. code-block:: python diff --git a/sources/pyside6/doc/tutorials/basictutorial/uifiles.rst b/sources/pyside6/doc/tutorials/basictutorial/uifiles.rst index aedaf2d6..c56593ee 100644 --- a/sources/pyside6/doc/tutorials/basictutorial/uifiles.rst +++ b/sources/pyside6/doc/tutorials/basictutorial/uifiles.rst @@ -192,7 +192,7 @@ The complete code of this example looks like this: Then to execute it we just need to run the following on a command prompt: -.. code-block:: python +.. code-block:: bash python main.py @@ -214,7 +214,7 @@ in the widget box and can be dragged onto the form just like Qt's widgets (see `Using Custom Widgets with Qt Designer `_ ). Normally, this requires implementing the widget as a plugin to Qt Designer written in C++ implementing its -`QDesignerCustomWidgetInterface `_ . +`QDesignerCustomWidgetInterface`_ . Qt for Python provides a simple interface for this which is similar to :meth:`registerCustomWidget()`. @@ -262,10 +262,10 @@ The code of the registration script looks as follows: QPyDesignerCustomWidgetCollection provides an implementation of -`QDesignerCustomWidgetCollectionInterface `_ +`QDesignerCustomWidgetCollectionInterface`_ exposing custom widgets to **Qt Designer** with static convenience functions for registering types or adding instances of -`QDesignerCustomWidgetInterface `_ . +`QDesignerCustomWidgetInterface`_ . The function :meth:`registerCustomWidget()` @@ -273,7 +273,7 @@ is used to register a widget type with **Qt Designer**. In the simple case, it can be used like ``QUiLoader.registerCustomWidget()``. It takes the custom widget type and some optional keyword arguments passing values that correspond to the getters of -`QDesignerCustomWidgetInterface `_ : +`QDesignerCustomWidgetInterface`_ : When launching **Qt Designer** via its launcher ``pyside6-designer``, the custom widget should be visible in the widget box. @@ -286,6 +286,9 @@ is registered for the custom widget. The example is a port of the corresponding C++ `Task Menu Extension Example `_ . +.. _QDesignerCustomWidgetCollectionInterface: https://doc.qt.io/qt-6/qdesignercustomwidgetcollectioninterface.html +.. _QDesignerCustomWidgetInterface: https://doc.qt.io/qt-6/qdesignercustomwidgetinterface.html + Troubleshooting the Qt Designer Plugin ++++++++++++++++++++++++++++++++++++++ diff --git a/sources/pyside6/doc/tutorials/expenses/expenses.rst b/sources/pyside6/doc/tutorials/expenses/expenses.rst index c674a611..2064488a 100644 --- a/sources/pyside6/doc/tutorials/expenses/expenses.rst +++ b/sources/pyside6/doc/tutorials/expenses/expenses.rst @@ -33,10 +33,10 @@ code block. .. code-block:: python :linenos: - if __name__ == "__main__": - app = QApplication([]) - # ... - sys.exit(app.exec()) + if __name__ == "__main__": + app = QApplication([]) + # ... + sys.exit(app.exec()) Now, to start the development, create an empty window called `MainWindow`. You could do that by defining a class that inherits from `QMainWindow`. diff --git a/sources/pyside6/doc/tutorials/index.rst b/sources/pyside6/doc/tutorials/index.rst index 72b72fc3..8a69a3c6 100644 --- a/sources/pyside6/doc/tutorials/index.rst +++ b/sources/pyside6/doc/tutorials/index.rst @@ -34,7 +34,7 @@ names and how they look like. .. grid-item-card:: Basic Connections :class-item: cover-img - :link: basictutorial/signal_and_slots.html + :link: basictutorial/signals_and_slots.html :img-top: basictutorial/signals_slots.png Signals and Slots diff --git a/sources/pyside6/doc/tutorials/portingguide/chapter1/chapter1.rst b/sources/pyside6/doc/tutorials/portingguide/chapter1/chapter1.rst index 2a3bd707..87fb9766 100644 --- a/sources/pyside6/doc/tutorials/portingguide/chapter1/chapter1.rst +++ b/sources/pyside6/doc/tutorials/portingguide/chapter1/chapter1.rst @@ -26,7 +26,7 @@ the tables. Port these helper functions first. Here is how the C++ and Python versions of these functions look like: C++ version ------------- +----------- .. literalinclude:: initdb.h :language: c++ @@ -34,7 +34,7 @@ C++ version :lines: 9-33 Python version ---------------- +-------------- .. literalinclude:: createdb.py :language: python @@ -45,16 +45,16 @@ Now that the helper functions are in place, port ``initDb``. Here is how the C++ and Python versions of this function looks like: -C++ version ------------- +C++ version (initDb) +-------------------- .. literalinclude:: initdb.h :language: c++ :linenos: :lines: 35-112 -Python version ---------------- +Python version (init_db) +------------------------ .. literalinclude:: createdb.py :language: python @@ -77,7 +77,7 @@ test it, add the following code to ``main.py`` and run it: Use the following command from the prompt to run: -.. code-block:: +.. code-block:: bash python main.py diff --git a/sources/pyside6/doc/tutorials/portingguide/chapter2/chapter2.rst b/sources/pyside6/doc/tutorials/portingguide/chapter2/chapter2.rst index fa6ef111..83ba3357 100644 --- a/sources/pyside6/doc/tutorials/portingguide/chapter2/chapter2.rst +++ b/sources/pyside6/doc/tutorials/portingguide/chapter2/chapter2.rst @@ -47,16 +47,16 @@ For example, painting stars to represent the rating for each book in the table. Here is how the reimplemented code looks like: -C++ version ------------- +C++ version (bookdelegate) +-------------------------- .. literalinclude:: bookdelegate.cpp :language: c++ :linenos: :lines: 22- -Python version ---------------- +Python version (bookdelegate) +----------------------------- .. literalinclude:: bookdelegate.py :language: python diff --git a/sources/pyside6/doc/tutorials/portingguide/chapter3/chapter3.rst b/sources/pyside6/doc/tutorials/portingguide/chapter3/chapter3.rst index 59ce6c9f..98d4d398 100644 --- a/sources/pyside6/doc/tutorials/portingguide/chapter3/chapter3.rst +++ b/sources/pyside6/doc/tutorials/portingguide/chapter3/chapter3.rst @@ -21,9 +21,9 @@ and add the following imports to it: To generate this Python code, run the following command on the prompt: -.. code-block:: +.. code-block:: bash - pyside6-uic bookwindow.ui -o ui_bookwindow.py + pyside6-uic bookwindow.ui -o ui_bookwindow.py Try porting the remaining code now. To begin with, here is how both the versions of the constructor code looks: @@ -101,9 +101,9 @@ image only. Now, run the ``pyside6-rcc`` tool on the ``books.qrc`` file to generate ``rc_books.py``. -.. code-block:: +.. code-block:: bash - pyside6-rcc books.qrc -o rc_books.py + pyside6-rcc books.qrc -o rc_books.py Once you have the Python script generated, make the following changes to ``bookdelegate.py`` and ``main.py``: diff --git a/sources/pyside6/doc/tutorials/qmlsqlintegration/qmlsqlintegration.rst b/sources/pyside6/doc/tutorials/qmlsqlintegration/qmlsqlintegration.rst index c664f2e4..eee3f807 100644 --- a/sources/pyside6/doc/tutorials/qmlsqlintegration/qmlsqlintegration.rst +++ b/sources/pyside6/doc/tutorials/qmlsqlintegration/qmlsqlintegration.rst @@ -144,7 +144,6 @@ remaining space that is left after accommodating the Pane. .. _attached properties: https://doc.qt.io/qt-5/qml-qtquick-layouts-layout.html .. _Layout.fillWidth: https://doc.qt.io/qt-5/qml-qtquick-layouts-layout.html#fillWidth-attached-prop .. _Layout.fillHeight: https://doc.qt.io/qt-5/qml-qtquick-layouts-layout.html#fillHeight-attached-prop -.. _ListView: https://doc.qt.io/qt-5/qml-qtquick-listview.html .. _Qt Quick QML Types: https://doc.qt.io/qt-5/qtquick-qmlmodule.html Let's look at the ``Listview`` in detail: diff --git a/sources/pyside6/doc/videos.rst b/sources/pyside6/doc/videos.rst index 82a78a06..919c9ff3 100644 --- a/sources/pyside6/doc/videos.rst +++ b/sources/pyside6/doc/videos.rst @@ -88,6 +88,12 @@ Conferences .. grid:: 1 3 3 3 :gutter: 2 + .. grid-item-card:: PyConES 2023 + :img-top: https://img.youtube.com/vi/XuqdTvisqkQ/mqdefault.jpg + :link: https://www.youtube.com/embed/XuqdTvisqkQ + + Asynchronous programming with asyncio and Qt + .. grid-item-card:: QtWS 2022 :img-top: https://img.youtube.com/vi/8wcdN1Iw1Uk/mqdefault.jpg :link: https://www.youtube.com/embed/8wcdN1Iw1Uk diff --git a/sources/pyside6/libpyside/CMakeLists.txt b/sources/pyside6/libpyside/CMakeLists.txt index 01a0a66b..e9fd0d56 100644 --- a/sources/pyside6/libpyside/CMakeLists.txt +++ b/sources/pyside6/libpyside/CMakeLists.txt @@ -5,7 +5,10 @@ project(libpyside) set(libpyside_libraries Qt::Core Qt::CorePrivate) +set(CMAKE_AUTOMOC ON) + set(libpyside_HEADERS # installed below + pysideqslotobject_p.h class_property.h dynamicqmetaobject.h feature_select.h @@ -41,6 +44,7 @@ set(libpyside_HEADERS # installed below ) set(libpyside_SRC + pysideqslotobject_p.cpp class_property.cpp dynamicqmetaobject.cpp feature_select.cpp diff --git a/sources/pyside6/libpyside/globalreceiverv2.cpp b/sources/pyside6/libpyside/globalreceiverv2.cpp index ddbd082e..09385645 100644 --- a/sources/pyside6/libpyside/globalreceiverv2.cpp +++ b/sources/pyside6/libpyside/globalreceiverv2.cpp @@ -10,7 +10,6 @@ #include #include -#include #include #include #include @@ -23,14 +22,6 @@ namespace PySide { -size_t qHash(const GlobalReceiverKey &k, size_t seed) -{ - QtPrivate::QHashCombine hash; - seed = hash(seed, k.object); - seed = hash(seed, k.method); - return seed; -} - class DynamicSlotDataV2 { Q_DISABLE_COPY_MOVE(DynamicSlotDataV2) diff --git a/sources/pyside6/libpyside/globalreceiverv2.h b/sources/pyside6/libpyside/globalreceiverv2.h index 0e78b9fe..24911c3d 100644 --- a/sources/pyside6/libpyside/globalreceiverv2.h +++ b/sources/pyside6/libpyside/globalreceiverv2.h @@ -9,6 +9,7 @@ #include "dynamicqmetaobject.h" #include +#include #include #include #include @@ -27,6 +28,11 @@ struct GlobalReceiverKey { const PyObject *object; const PyObject *method; + + friend constexpr size_t qHash(GlobalReceiverKey k, size_t seed = 0) noexcept + { + return qHashMulti(seed, k.object, k.method); + } }; inline bool operator==(const GlobalReceiverKey &k1, const GlobalReceiverKey &k2) @@ -39,8 +45,6 @@ inline bool operator!=(const GlobalReceiverKey &k1, const GlobalReceiverKey &k2) return k1.object != k2.object || k1.method != k2.method; } -size_t qHash(const GlobalReceiverKey &k, size_t seed = 0); - /// A class used to link C++ Signals to non C++ slots (Python callbacks) by /// providing fake slots for QObject::connect(). /// It keeps a Python callback and the list of QObject senders. It is stored diff --git a/sources/pyside6/libpyside/pysideqslotobject_p.cpp b/sources/pyside6/libpyside/pysideqslotobject_p.cpp new file mode 100644 index 00000000..914be898 --- /dev/null +++ b/sources/pyside6/libpyside/pysideqslotobject_p.cpp @@ -0,0 +1,36 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "pysideqslotobject_p.h" + +#include +#include + +namespace PySide +{ + +void PySideQSlotObject::impl(int which, QSlotObjectBase *this_, QObject *receiver, + void **args, bool *ret) +{ + auto self = static_cast(this_); + switch (which) { + case Destroy: + delete self; + break; + case Call: + { + Shiboken::GilState state; + Shiboken::AutoDecRef arglist(PyTuple_New(0)); + Shiboken::AutoDecRef ret(PyObject_CallObject(self->callable, arglist)); + break; + } + case Compare: + case NumOperations: + Q_UNUSED(receiver); + Q_UNUSED(args); + Q_UNUSED(ret); + break; + } +} + +} // namespace PySide diff --git a/sources/pyside6/libpyside/pysideqslotobject_p.h b/sources/pyside6/libpyside/pysideqslotobject_p.h new file mode 100644 index 00000000..d7d25850 --- /dev/null +++ b/sources/pyside6/libpyside/pysideqslotobject_p.h @@ -0,0 +1,39 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef PYSIDEQSLOTOBJECT_P_H +#define PYSIDEQSLOTOBJECT_P_H + +#include "pysidemacros.h" +#include + +#include +#include + +namespace PySide +{ + +class PySideQSlotObject : public QtPrivate::QSlotObjectBase +{ + PyObject *callable; + + static void impl(int which, QSlotObjectBase *this_, QObject *receiver, void **args, bool *ret); + +public: + PySideQSlotObject(PyObject *callable) : QtPrivate::QSlotObjectBase(&impl), callable(callable) + { + Py_INCREF(callable); + } + + ~PySideQSlotObject() + { + auto gstate = PyGILState_Ensure(); + Py_DECREF(callable); + PyGILState_Release(gstate); + } +}; + + +} // namespace PySide + +#endif // PYSIDEQSLOTOBJECT_P_H diff --git a/sources/pyside6/libpyside/pysidesignal.cpp b/sources/pyside6/libpyside/pysidesignal.cpp index 4653e394..f8c23b91 100644 --- a/sources/pyside6/libpyside/pysidesignal.cpp +++ b/sources/pyside6/libpyside/pysidesignal.cpp @@ -103,24 +103,9 @@ static std::optional parseArgumentNames(PyObject *argArguments) } namespace PySide::Signal { - //aux - class SignalSignature { - public: - SignalSignature() = default; - explicit SignalSignature(QByteArray parameterTypes) : - m_parameterTypes(std::move(parameterTypes)) {} - explicit SignalSignature(QByteArray parameterTypes, QMetaMethod::Attributes attributes) : - m_parameterTypes(std::move(parameterTypes)), - m_attributes(attributes) {} - - QByteArray m_parameterTypes; - QMetaMethod::Attributes m_attributes = QMetaMethod::Compatibility; - }; - static QByteArray buildSignature(const QByteArray &, const QByteArray &); - static void appendSignature(PySideSignal *, const SignalSignature &); static void instanceInitialize(PySideSignalInstance *, PyObject *, PySideSignal *, PyObject *, int); - static QByteArray parseSignature(PyObject *); + static PySideSignalData::Signature parseSignature(PyObject *); static PyObject *buildQtCompatible(const QByteArray &); } // PySide::Signal @@ -292,19 +277,12 @@ static int signalTpInit(PyObject *obSelf, PyObject *args, PyObject *kwds) PyObject *arg = PyTuple_GET_ITEM(args, i); if (PySequence_Check(arg) && !Shiboken::String::check(arg) && !PyEnumMeta_Check(arg)) { tupledArgs = true; - const auto sig = PySide::Signal::parseSignature(arg); - PySide::Signal::appendSignature( - self, - PySide::Signal::SignalSignature(sig)); + self->data->signatures.append(PySide::Signal::parseSignature(arg)); } } - if (!tupledArgs) { - const auto sig = PySide::Signal::parseSignature(args); - PySide::Signal::appendSignature( - self, - PySide::Signal::SignalSignature(sig)); - } + if (!tupledArgs) + self->data->signatures.append(PySide::Signal::parseSignature(args)); return 0; } @@ -328,7 +306,7 @@ static PyObject *signalGetItem(PyObject *obSelf, PyObject *key) auto self = reinterpret_cast(obSelf); QByteArray sigKey; if (key) { - sigKey = PySide::Signal::parseSignature(key); + sigKey = PySide::Signal::parseSignature(key).signature; } else { sigKey = self->data == nullptr || self->data->signatures.isEmpty() ? PySide::Signal::voidType() : self->data->signatures.constFirst().signature; @@ -648,7 +626,7 @@ static PyObject *signalInstanceGetItem(PyObject *self, PyObject *key) { auto *firstSignal = reinterpret_cast(self); const auto &sigName = firstSignal->d->signalName; - const auto sigKey = PySide::Signal::parseSignature(key); + const auto sigKey = PySide::Signal::parseSignature(key).signature; const auto sig = PySide::Signal::buildSignature(sigName, sigKey); for (auto *data = firstSignal; data != nullptr; data = data->d->next) { if (data->d->signature == sig) { @@ -984,27 +962,24 @@ static QByteArray buildSignature(const QByteArray &name, const QByteArray &signa return QMetaObject::normalizedSignature(name + '(' + signature + ')'); } -static QByteArray parseSignature(PyObject *args) +static PySideSignalData::Signature parseSignature(PyObject *args) { - if (args && (Shiboken::String::check(args) || !PyTuple_Check(args))) - return getTypeName(args); + PySideSignalData::Signature result{{}, QMetaMethod::Compatibility}; + if (args && (Shiboken::String::check(args) || !PyTuple_Check(args))) { + result.signature = getTypeName(args); + return result; + } - QByteArray signature; for (Py_ssize_t i = 0, i_max = PySequence_Size(args); i < i_max; i++) { Shiboken::AutoDecRef arg(PySequence_GetItem(args, i)); const auto typeName = getTypeName(arg); if (!typeName.isEmpty()) { - if (!signature.isEmpty()) - signature += ','; - signature += typeName; + if (!result.signature.isEmpty()) + result.signature += ','; + result.signature += typeName; } } - return signature; -} - -static void appendSignature(PySideSignal *self, const SignalSignature &signature) -{ - self->data->signatures.append({signature.m_parameterTypes, signature.m_attributes}); + return result; } static void sourceGone(void *data) @@ -1110,26 +1085,6 @@ PySideSignalInstance *newObjectFromMethod(PyObject *source, const QList -static typename T::value_type join(T t, const char *sep) -{ - typename T::value_type res; - if (t.isEmpty()) - return res; - - typename T::const_iterator it = t.begin(); - typename T::const_iterator end = t.end(); - res += *it; - ++it; - - while (it != end) { - res += sep; - res += *it; - ++it; - } - return res; -} - static void _addSignalToWrapper(PyTypeObject *wrapperType, const char *signalName, PySideSignal *signal) { Shiboken::AutoDecRef tpDict(PepType_GetDict(wrapperType)); @@ -1143,9 +1098,10 @@ static void _addSignalToWrapper(PyTypeObject *wrapperType, const char *signalNam } // This function is used by qStableSort to promote empty signatures -static bool compareSignals(const SignalSignature &sig1, const SignalSignature &) +static bool compareSignals(const PySideSignalData::Signature &sig1, + const PySideSignalData::Signature &sig2) { - return sig1.m_parameterTypes.isEmpty(); + return sig1.signature.isEmpty() && !sig2.signature.isEmpty(); } static PyObject *buildQtCompatible(const QByteArray &signature) @@ -1156,7 +1112,8 @@ static PyObject *buildQtCompatible(const QByteArray &signature) void registerSignals(PyTypeObject *pyObj, const QMetaObject *metaObject) { - using SignalSigMap = QHash >; + using Signature = PySideSignalData::Signature; + using SignalSigMap = QHash>; SignalSigMap signalsFound; for (int i = metaObject->methodOffset(), max = metaObject->methodCount(); i < max; ++i) { QMetaMethod method = metaObject->method(i); @@ -1164,10 +1121,9 @@ void registerSignals(PyTypeObject *pyObj, const QMetaObject *metaObject) if (method.methodType() == QMetaMethod::Signal) { QByteArray methodName(method.methodSignature()); methodName.chop(methodName.size() - methodName.indexOf('(')); - SignalSignature signature; - signature.m_parameterTypes = join(method.parameterTypes(), ","); + Signature signature{method.parameterTypes().join(','), {}}; if (method.attributes() & QMetaMethod::Cloned) - signature.m_attributes = QMetaMethod::Cloned; + signature.attributes = QMetaMethod::Cloned; signalsFound[methodName] << signature; } } @@ -1181,12 +1137,9 @@ void registerSignals(PyTypeObject *pyObj, const QMetaObject *metaObject) self->homonymousMethod = nullptr; // Empty signatures comes first! So they will be the default signal signature - std::stable_sort(it.value().begin(), it.value().end(), &compareSignals); - const auto endJ = it.value().cend(); - for (auto j = it.value().cbegin(); j != endJ; ++j) { - const SignalSignature &sig = *j; - appendSignature(self, sig); - } + self->data->signatures = it.value(); + std::stable_sort(self->data->signatures.begin(), + self->data->signatures.end(), &compareSignals); _addSignalToWrapper(pyObj, it.key(), self); Py_DECREF(reinterpret_cast(self)); diff --git a/sources/pyside6/libpyside/pysidesignal_p.h b/sources/pyside6/libpyside/pysidesignal_p.h index 37ccb58d..8a0feea4 100644 --- a/sources/pyside6/libpyside/pysidesignal_p.h +++ b/sources/pyside6/libpyside/pysidesignal_p.h @@ -13,8 +13,8 @@ struct PySideSignalData { struct Signature { - QByteArray signature; - int attributes; + QByteArray signature; // ','-separated list of parameter types + unsigned short attributes; }; QByteArray signalName; @@ -39,10 +39,10 @@ struct PySideSignalInstancePrivate { QByteArray signalName; QByteArray signature; - int attributes = 0; PyObject *source = nullptr; PyObject *homonymousMethod = nullptr; PySideSignalInstance *next = nullptr; + unsigned short attributes = 0; }; namespace PySide::Signal { diff --git a/sources/pyside6/libpyside/qobjectconnect.cpp b/sources/pyside6/libpyside/qobjectconnect.cpp index 15b71766..c47c96eb 100644 --- a/sources/pyside6/libpyside/qobjectconnect.cpp +++ b/sources/pyside6/libpyside/qobjectconnect.cpp @@ -3,6 +3,7 @@ #include "qobjectconnect.h" #include "pysideqobject.h" +#include "pysideqslotobject_p.h" #include "pysidesignal.h" #include "pysideutils.h" #include "signalmanager.h" @@ -15,6 +16,8 @@ #include #include +#include + #include static bool isMethodDecorator(PyObject *method, bool is_pymethod, PyObject *self) @@ -259,6 +262,46 @@ QMetaObject::Connection qobjectConnectCallback(QObject *source, const char *sign return connection; } +QMetaObject::Connection qobjectConnectCallback(QObject *source, const char *signal, QObject *context, + PyObject *callback, Qt::ConnectionType type) +{ + if (!signal || !PySide::Signal::checkQtSignal(signal)) + return {}; + + const int signalIndex = + PySide::SignalManager::registerMetaMethodGetIndex(source, signal + 1, + QMetaMethod::Signal); + if (signalIndex == -1) + return {}; + + // Extract receiver from callback + const GetReceiverResult receiver = getReceiver(source, signal + 1, callback); + if (receiver.receiver == nullptr && receiver.self == nullptr) + return {}; + + PySide::SignalManager &signalManager = PySide::SignalManager::instance(); + + PySideQSlotObject *slotObject = new PySideQSlotObject(callback); + + QMetaObject::Connection connection{}; + Py_BEGIN_ALLOW_THREADS // PYSIDE-2367, prevent threading deadlocks with connectNotify() + connection = QObjectPrivate::connect(source, signalIndex, context, slotObject, type); + Py_END_ALLOW_THREADS + if (!connection) { + if (receiver.usingGlobalReceiver) + signalManager.releaseGlobalReceiver(source, receiver.receiver); + return {}; + } + + Q_ASSERT(receiver.receiver); + if (receiver.usingGlobalReceiver) + signalManager.notifyGlobalReceiver(receiver.receiver); + + const QMetaMethod signalMethod = receiver.receiver->metaObject()->method(signalIndex); + static_cast(source)->connectNotify(signalMethod); + return connection; +} + bool qobjectDisconnectCallback(QObject *source, const char *signal, PyObject *callback) { if (!PySide::Signal::checkQtSignal(signal)) diff --git a/sources/pyside6/libpyside/qobjectconnect.h b/sources/pyside6/libpyside/qobjectconnect.h index 70b86223..c99b8006 100644 --- a/sources/pyside6/libpyside/qobjectconnect.h +++ b/sources/pyside6/libpyside/qobjectconnect.h @@ -33,6 +33,11 @@ PYSIDE_API QMetaObject::Connection qobjectConnectCallback(QObject *source, const char *signal, PyObject *callback, Qt::ConnectionType type); +/// Helpers for QObject::connect(): Make a connection to a Python callback and a context object +PYSIDE_API QMetaObject::Connection + qobjectConnectCallback(QObject *source, const char *signal, QObject *context, + PyObject *callback, Qt::ConnectionType type); + /// Helpers for QObject::disconnect(): Disconnect a Python callback PYSIDE_API bool qobjectDisconnectCallback(QObject *source, const char *signal, PyObject *callback); diff --git a/sources/pyside6/libpysideqml/pysideqmlregistertype.h b/sources/pyside6/libpysideqml/pysideqmlregistertype.h index 1e6c7d28..8d0e8065 100644 --- a/sources/pyside6/libpysideqml/pysideqmlregistertype.h +++ b/sources/pyside6/libpysideqml/pysideqmlregistertype.h @@ -7,11 +7,14 @@ #include "pysideqmlmacros.h" #include +#include +QT_BEGIN_NAMESPACE namespace QQmlPrivate { struct RegisterType; } +QT_END_NAMESPACE namespace PySide::Qml { @@ -80,7 +83,7 @@ PYSIDEQML_API PyObject *qmlSingletonMacro(PyObject *pyObj); // Used by QtQuick module to fill the QQmlPrivate::RegisterType::parserStatusCast, // valueSourceCast and valueInterceptorCast fields with the correct values. using QuickRegisterItemFunction = - bool (*)(PyObject *pyObj, QQmlPrivate::RegisterType *); + bool (*)(PyObject *pyObj, QT_PREPEND_NAMESPACE(QQmlPrivate::RegisterType) *); PYSIDEQML_API QuickRegisterItemFunction getQuickRegisterItemFunction(); PYSIDEQML_API void setQuickRegisterItemFunction(QuickRegisterItemFunction function); diff --git a/sources/pyside6/plugins/designer/designercustomwidgets.cpp b/sources/pyside6/plugins/designer/designercustomwidgets.cpp index 6491251b..ccff635a 100644 --- a/sources/pyside6/plugins/designer/designercustomwidgets.cpp +++ b/sources/pyside6/plugins/designer/designercustomwidgets.cpp @@ -1,6 +1,7 @@ // Copyright (C) 2021 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +#undef slots #include // Include before Qt headers due to 'slots' macro definition #include "designercustomwidgets.h" diff --git a/sources/pyside6/tests/CMakeLists.txt b/sources/pyside6/tests/CMakeLists.txt index e18b6254..c581a8d0 100644 --- a/sources/pyside6/tests/CMakeLists.txt +++ b/sources/pyside6/tests/CMakeLists.txt @@ -54,6 +54,10 @@ add_subdirectory(support) add_subdirectory(tools/metaobjectdump) add_subdirectory(tools/pyside6-deploy) +if(UNIX AND NOT APPLE) + add_subdirectory(tools/pyside6-android-deploy) +endif() + if (NOT DISABLE_QtQuick) add_subdirectory(tools/pyside6-qml) endif() diff --git a/sources/pyside6/tests/QtAsyncio/qasyncio_test.py b/sources/pyside6/tests/QtAsyncio/qasyncio_test.py index 95348381..f3c97128 100644 --- a/sources/pyside6/tests/QtAsyncio/qasyncio_test.py +++ b/sources/pyside6/tests/QtAsyncio/qasyncio_test.py @@ -12,7 +12,7 @@ from PySide6.QtAsyncio import QAsyncioEventLoopPolicy class QAsyncioTestCase(unittest.TestCase): async def sleep(self, output): output += "Hello" - await asyncio.sleep(1) + await asyncio.sleep(0.2) output += "World" async def gather(self, output): diff --git a/sources/pyside6/tests/QtAsyncio/qasyncio_test_cancel_task.py b/sources/pyside6/tests/QtAsyncio/qasyncio_test_cancel_task.py new file mode 100644 index 00000000..7ef2bb90 --- /dev/null +++ b/sources/pyside6/tests/QtAsyncio/qasyncio_test_cancel_task.py @@ -0,0 +1,46 @@ +# Copyright (C) 2024 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +'''Test cases for QtAsyncio''' + +import asyncio +import unittest + +import PySide6.QtAsyncio as QtAsyncio + + +class QAsyncioTestCaseCancelTask(unittest.TestCase): + # Taken from https://docs.python.org/3/library/asyncio-task.html#asyncio.Task.cancel + + async def cancel_me(self, output): + output += "(1) cancel_me(): before sleep" + + try: + await asyncio.sleep(10) + except asyncio.CancelledError: + output += "(2) cancel_me(): cancel sleep" + raise + finally: + output += "(3) cancel_me(): after sleep" + + async def main(self, output): + task = asyncio.create_task(self.cancel_me(output)) + await asyncio.sleep(0.1) + task.cancel() + try: + await task + except asyncio.CancelledError: + output += "(4) main(): cancel_me is cancelled now" + + def test_await_tasks(self): + output_expected = [] + output_real = [] + + asyncio.run(self.main(output_expected)) + QtAsyncio.run(self.main(output_real), keep_running=False) + + self.assertEqual(output_real, output_expected) + + +if __name__ == '__main__': + unittest.main() diff --git a/sources/pyside6/tests/QtAsyncio/qasyncio_test_chain.py b/sources/pyside6/tests/QtAsyncio/qasyncio_test_chain.py index f45b51a7..a0a94972 100644 --- a/sources/pyside6/tests/QtAsyncio/qasyncio_test_chain.py +++ b/sources/pyside6/tests/QtAsyncio/qasyncio_test_chain.py @@ -22,8 +22,8 @@ class QAsyncioTestCaseChain(unittest.TestCase): return result async def chain(self, output, n): - link1 = await self.link(output, n, 1) - link2 = await self.link(output, n, 2) + link1 = await self.link(output, n, 0.2) + link2 = await self.link(output, n, 0.5) output += f"chain {n}: {link1} -> {link2} " async def gather(self, output, *args): diff --git a/sources/pyside6/tests/QtAsyncio/qasyncio_test_executor.py b/sources/pyside6/tests/QtAsyncio/qasyncio_test_executor.py index f343aa73..25e680b3 100644 --- a/sources/pyside6/tests/QtAsyncio/qasyncio_test_executor.py +++ b/sources/pyside6/tests/QtAsyncio/qasyncio_test_executor.py @@ -27,7 +27,8 @@ class QAsyncioTestCaseExecutor(unittest.TestCase): async def run_asyncio_executor(self): main_thread = QThread.currentThread() with ThreadPoolExecutor(max_workers=2) as executor: - result = await asyncio.get_running_loop().run_in_executor(executor, self.blocking_function) + result = await asyncio.get_running_loop().run_in_executor( + executor, self.blocking_function) # Assert that we are back to the main thread. self.assertEqual(QThread.currentThread(), main_thread) diff --git a/sources/pyside6/tests/QtAsyncio/qasyncio_test_queues.py b/sources/pyside6/tests/QtAsyncio/qasyncio_test_queues.py index 38827b0f..0bd98c36 100644 --- a/sources/pyside6/tests/QtAsyncio/qasyncio_test_queues.py +++ b/sources/pyside6/tests/QtAsyncio/qasyncio_test_queues.py @@ -14,24 +14,26 @@ from PySide6.QtAsyncio import QAsyncioEventLoopPolicy class QAsyncioTestCaseQueues(unittest.TestCase): async def produce(self, output, queue): - for _ in range(random.randint(0, 3)): - await asyncio.sleep(random.randint(0, 2)) + for _ in range(random.randint(0, 2)): + await asyncio.sleep(random.random()) await queue.put(self.i) output += f"{self.i} added to queue\n" self.i += 1 async def consume(self, output, queue): while True: - await asyncio.sleep(random.randint(0, 2)) + await asyncio.sleep(random.random()) i = await queue.get() output += f"{i} pulled from queue\n" queue.task_done() async def main(self, output1, output2, num_producers, num_consumers): self.i = 0 - queue = asyncio.Queue() # type: asyncio.Queue - producers = [asyncio.create_task(self.produce(output1, queue)) for _ in range(num_producers)] - consumers = [asyncio.create_task(self.consume(output2, queue)) for _ in range(num_consumers)] + queue = asyncio.Queue() + producers = [ + asyncio.create_task(self.produce(output1, queue)) for _ in range(num_producers)] + consumers = [ + asyncio.create_task(self.consume(output2, queue)) for _ in range(num_consumers)] await asyncio.gather(*producers) await queue.join() for consumer in consumers: diff --git a/sources/pyside6/tests/QtAsyncio/qasyncio_test_threadsafe.py b/sources/pyside6/tests/QtAsyncio/qasyncio_test_threadsafe.py new file mode 100644 index 00000000..5b52db23 --- /dev/null +++ b/sources/pyside6/tests/QtAsyncio/qasyncio_test_threadsafe.py @@ -0,0 +1,58 @@ +# Copyright (C) 2023 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +'''Test cases for QtAsyncio''' + +import unittest +import asyncio +import threading +import time + +from PySide6.QtAsyncio import QAsyncioEventLoopPolicy + + +class QAsyncioTestCaseThreadsafe(unittest.TestCase): + + def setUp(self) -> None: + super().setUp() + asyncio.set_event_loop_policy(QAsyncioEventLoopPolicy()) + self.loop_event = asyncio.Event() + + def thread_target(self, is_threadsafe): + time.sleep(1) + if is_threadsafe: + # call_soon_threadsafe() wakes the loop that is in another thread, so the + # loop checks the event and will not hang. + asyncio.get_event_loop().call_soon_threadsafe(self.loop_event.set) + else: + # call_soon() does not wake the loop that is in another thread, and so the + # loop keeps waiting without checking the event and will hang. + asyncio.get_event_loop().call_soon(self.loop_event.set) + + async def coro(self, is_threadsafe): + thread = threading.Thread(target=self.thread_target, args=(is_threadsafe,)) + thread.start() + + task = asyncio.create_task(self.loop_event.wait()) + + # The timeout is necessary because the loop will hang for the non-threadsafe case. + done, pending = await asyncio.wait([task], timeout=2) + + thread.join() + + if is_threadsafe: + self.assertEqual(len(done), 1) + self.assertEqual(len(pending), 0) + else: + self.assertEqual(len(done), 0) + self.assertEqual(len(pending), 1) + + def test_not_threadsafe(self): + asyncio.run(self.coro(False)) + + def test_threadsafe(self): + asyncio.run(self.coro(True)) + + +if __name__ == '__main__': + unittest.main() diff --git a/sources/pyside6/tests/QtAsyncio/qasyncio_test_time.py b/sources/pyside6/tests/QtAsyncio/qasyncio_test_time.py index 60ec2f06..07a12664 100644 --- a/sources/pyside6/tests/QtAsyncio/qasyncio_test_time.py +++ b/sources/pyside6/tests/QtAsyncio/qasyncio_test_time.py @@ -33,7 +33,7 @@ class QAsyncioTestCaseTime(unittest.TestCase): asyncio.set_event_loop_policy(QAsyncioEventLoopPolicy()) loop = asyncio.new_event_loop() - end_time = loop.time() + 5.0 + end_time = loop.time() + 3.0 loop.call_soon(self.display_date, end_time, loop) try: diff --git a/sources/pyside6/tests/QtBluetooth/localdevice.py b/sources/pyside6/tests/QtBluetooth/localdevice.py index b09cc12b..21801106 100644 --- a/sources/pyside6/tests/QtBluetooth/localdevice.py +++ b/sources/pyside6/tests/QtBluetooth/localdevice.py @@ -15,9 +15,11 @@ init_test_paths(False) from PySide6.QtBluetooth import QBluetoothLocalDevice + class QBluetoothLocalDeviceTest(unittest.TestCase): def testInitialization(self): device = QBluetoothLocalDevice() + if __name__ == '__main__': unittest.main() diff --git a/sources/pyside6/tests/QtCore/bug_515.py b/sources/pyside6/tests/QtCore/bug_515.py index 52293b80..4e270a44 100644 --- a/sources/pyside6/tests/QtCore/bug_515.py +++ b/sources/pyside6/tests/QtCore/bug_515.py @@ -26,7 +26,7 @@ def _cleanup(): def _checkCleanup(): global callCleanup - assert(callCleanup) + assert (callCleanup) app = QCoreApplication([]) diff --git a/sources/pyside6/tests/QtCore/classinfo_test.py b/sources/pyside6/tests/QtCore/classinfo_test.py index 30d141ac..0b0a0b4b 100644 --- a/sources/pyside6/tests/QtCore/classinfo_test.py +++ b/sources/pyside6/tests/QtCore/classinfo_test.py @@ -33,7 +33,7 @@ class TestClassInfo(unittest.TestCase): self.assertEqual(ci.value(), 'http://www.pyside.org') def test_dictionary(self): - @ClassInfo({'author':'pyside', 'author company':'The Qt Company'}) + @ClassInfo({'author': 'pyside', 'author company': 'The Qt Company'}) class MyObject(QObject): pass @@ -50,11 +50,11 @@ class TestClassInfo(unittest.TestCase): self.assertEqual(ci.value(), 'The Qt Company') def test_verify_metadata_types(self): - valid_dict = { '123': '456' } + valid_dict = {'123': '456'} - invalid_dict_1 = { '123': 456 } - invalid_dict_2 = { 123: 456 } - invalid_dict_3 = { 123: '456' } + invalid_dict_1 = {'123': 456} + invalid_dict_2 = {123: 456} + invalid_dict_3 = {123: '456'} ClassInfo(**valid_dict) @@ -88,6 +88,7 @@ class TestClassInfo(unittest.TestCase): def test_can_only_be_used_on_qobjects(self): def make_info(): return ClassInfo(author='pyside') + def test_function(): pass self.assertRaises(TypeError, make_info(), test_function) diff --git a/sources/pyside6/tests/QtCore/feature_with_uic/window.py b/sources/pyside6/tests/QtCore/feature_with_uic/window.py index 6632fbaf..c55bcaf9 100644 --- a/sources/pyside6/tests/QtCore/feature_with_uic/window.py +++ b/sources/pyside6/tests/QtCore/feature_with_uic/window.py @@ -28,7 +28,6 @@ class Ui_MainWindow(object): self.verticalLayout.addWidget(self.pushButton) - self.horizontalLayout.addLayout(self.verticalLayout) MainWindow.setCentralWidget(self.centralwidget) diff --git a/sources/pyside6/tests/QtCore/loggingcategorymacros_test.py b/sources/pyside6/tests/QtCore/loggingcategorymacros_test.py index fa85d95e..a808f0c3 100644 --- a/sources/pyside6/tests/QtCore/loggingcategorymacros_test.py +++ b/sources/pyside6/tests/QtCore/loggingcategorymacros_test.py @@ -17,6 +17,7 @@ from PySide6.QtCore import (QLoggingCategory, QtMsgType, qCDebug, qCWarning, qCI param = None + def handler(msgt, ctx, msg): global param param = ctx.category + ": " + msg.strip() @@ -70,7 +71,6 @@ class TestQLoggingCategory(unittest.TestCase): qCWarning(self.criticalCategory, f"devices: {self.no_devices}") self.assertEqual(param, "warning.log: devices: 2") - def test_qCritical(self): qCCritical(self.defaultCategory, "no device") self.assertEqual(param, "default: no device") diff --git a/sources/pyside6/tests/QtCore/multiple_feature_test.py b/sources/pyside6/tests/QtCore/multiple_feature_test.py index 4b6b0566..0f51ace6 100644 --- a/sources/pyside6/tests/QtCore/multiple_feature_test.py +++ b/sources/pyside6/tests/QtCore/multiple_feature_test.py @@ -30,6 +30,7 @@ There is much more to come. MethodDescriptorType = type(str.split) + def xprint(*args, **kw): if "-v" in sys.argv: print(*args, **kw) diff --git a/sources/pyside6/tests/QtCore/qcbor_test.py b/sources/pyside6/tests/QtCore/qcbor_test.py index 6c7606dd..312c9e5c 100644 --- a/sources/pyside6/tests/QtCore/qcbor_test.py +++ b/sources/pyside6/tests/QtCore/qcbor_test.py @@ -15,7 +15,7 @@ from init_paths import init_test_paths init_test_paths(False) from PySide6.QtCore import (QByteArray, QCborStreamReader, QCborStreamWriter, - QCborTag, QCborValue) + QCborTag, QCborValue) class TestCbor(unittest.TestCase): diff --git a/sources/pyside6/tests/QtCore/qobject_parent_test.py b/sources/pyside6/tests/QtCore/qobject_parent_test.py index 160426b0..6265f77b 100644 --- a/sources/pyside6/tests/QtCore/qobject_parent_test.py +++ b/sources/pyside6/tests/QtCore/qobject_parent_test.py @@ -105,10 +105,9 @@ class ParentCase(unittest.TestCase): for i, child in enumerate(children): self.assertEqual(child, parent.findChild(QObject, f'object{i}')) - def testFindChildOptions(self): parent = QObject() - child = QObject(parent) + child = QObject(parent) nested_child_name = 'nestedChild' nested_child = QObject(child) nested_child.setObjectName(nested_child_name) diff --git a/sources/pyside6/tests/QtCore/qresource_test.py b/sources/pyside6/tests/QtCore/qresource_test.py index 67285adc..1cdd2c78 100644 --- a/sources/pyside6/tests/QtCore/qresource_test.py +++ b/sources/pyside6/tests/QtCore/qresource_test.py @@ -31,7 +31,7 @@ class ResourcesUsage(unittest.TestCase): if carriage_return != -1: orig.remove(carriage_return, 1) - f = QFile(':/quote.txt') #|QIODevice.Text + f = QFile(':/quote.txt') # |QIODevice.Text self.assertTrue(f.open(QIODevice.ReadOnly), f.errorString()) copy = f.readAll() f.close() diff --git a/sources/pyside6/tests/QtCore/qtimer_singleshot_test.py b/sources/pyside6/tests/QtCore/qtimer_singleshot_test.py index 1392281f..9718a242 100644 --- a/sources/pyside6/tests/QtCore/qtimer_singleshot_test.py +++ b/sources/pyside6/tests/QtCore/qtimer_singleshot_test.py @@ -85,6 +85,17 @@ class TestSingleShot(UsesQApplication): self.assertTrue(thread.called) self.assertEqual(self.qthread, thread.qthread) + def testSingleShotWithContextZero(self): + thread = ThreadForContext() + thread.start() + thread.context.moveToThread(thread) + QTimer.singleShot(0, thread.context, self.callback) + self.app.exec() + thread.wait() + self.assertTrue(self.called) + self.assertTrue(thread.called) + self.assertEqual(self.qthread, thread.qthread) + class SigEmitter(QObject): diff --git a/sources/pyside6/tests/QtCore/snake_prop_feature_test.py b/sources/pyside6/tests/QtCore/snake_prop_feature_test.py index 4178c7a1..82087ab9 100644 --- a/sources/pyside6/tests/QtCore/snake_prop_feature_test.py +++ b/sources/pyside6/tests/QtCore/snake_prop_feature_test.py @@ -60,7 +60,7 @@ class FeatureTest(unittest.TestCase): window.modal from __feature__ import snake_case, true_property - #PYSIDE-1548: Make sure that another import does not clear the features. + # PYSIDE-1548: Make sure that another import does not clear the features. import sys self.assertTrue(isinstance(QWidget.modal, property)) diff --git a/sources/pyside6/tests/QtDataVisualization/datavisualization_test.py b/sources/pyside6/tests/QtDataVisualization/datavisualization_test.py index eb089294..98ada6d4 100644 --- a/sources/pyside6/tests/QtDataVisualization/datavisualization_test.py +++ b/sources/pyside6/tests/QtDataVisualization/datavisualization_test.py @@ -82,8 +82,8 @@ class QtDataVisualizationTestCase(UsesQApplication): self.assertTrue(data_proxy.rowCount(), 4) def testDefaultSurfaceFormat(self): - format = qDefaultSurfaceFormat(True) - print(format) + format = qDefaultSurfaceFormat(True) + print(format) def testQValue3DAxisFormatter(self): """PYSIDE-2025: Test the added setters of QValue3DAxisFormatter.""" diff --git a/sources/pyside6/tests/QtGui/qcolor_test.py b/sources/pyside6/tests/QtGui/qcolor_test.py index 336d58f8..bbd558f1 100644 --- a/sources/pyside6/tests/QtGui/qcolor_test.py +++ b/sources/pyside6/tests/QtGui/qcolor_test.py @@ -41,7 +41,7 @@ class QColorGetTest(unittest.TestCase): def testGetCmykF(self): # not supported by colorsys for x, y in zip(self.color.getCmykF(), (170 / 255.0, 85 / 255.0, 0, 195 / 255.0, 80 / 255.0)): - self.assertTrue(x - y < 1/10000.0) + self.assertTrue(x - y < 1 / 10000.0) class QColorQRgbConstructor(unittest.TestCase): diff --git a/sources/pyside6/tests/QtGui/qfontmetrics_test.py b/sources/pyside6/tests/QtGui/qfontmetrics_test.py index 52ce810d..98c4c8f9 100644 --- a/sources/pyside6/tests/QtGui/qfontmetrics_test.py +++ b/sources/pyside6/tests/QtGui/qfontmetrics_test.py @@ -91,8 +91,8 @@ class BoundingRectTest(QFontMetricsTest): '''QFontMetrics.boundingRect(QRect, ...) - type error''' arg = QRect(0, 0, 100, 200) self.assertRaises(TypeError, self.metrics.boundingRect, arg, - Qt.TextExpandTabs | Qt.AlignLeft, - 'PySide by INdT', 20, ['aaaa', 'ase']) + Qt.TextExpandTabs | Qt.AlignLeft, + 'PySide by INdT', 20, ['aaaa', 'ase']) class SizeTest(QFontMetricsTest): @@ -196,8 +196,8 @@ class FSizeTest(QFontMetricsFTest): def testTypeError(self): '''QFontMetricsF.size - type error''' self.assertRaises(TypeError, self.metrics.size, - Qt.TextExpandTabs | Qt.AlignLeft, - 'PySide by INdT', 20, ['aaaa', 'ase']) + Qt.TextExpandTabs | Qt.AlignLeft, + 'PySide by INdT', 20, ['aaaa', 'ase']) class QCharTest(QFontMetricsFTest): diff --git a/sources/pyside6/tests/QtGui/qicon_test.py b/sources/pyside6/tests/QtGui/qicon_test.py index 80fc5a56..18ef3d81 100644 --- a/sources/pyside6/tests/QtGui/qicon_test.py +++ b/sources/pyside6/tests/QtGui/qicon_test.py @@ -26,6 +26,7 @@ class QIconCtorWithNoneTest(TimedQGuiApplication): PIX_PATH = os.fspath(Path(__file__).resolve().parents[2] / "doc/tutorials/basictutorial/icons.png") + class QIconAddPixmapTest(TimedQGuiApplication): '''PYSIDE-1669: check that addPixmap works''' diff --git a/sources/pyside6/tests/QtOpenGL/qopenglwindow_test.py b/sources/pyside6/tests/QtOpenGL/qopenglwindow_test.py index e2e87437..3e6bc4c9 100644 --- a/sources/pyside6/tests/QtOpenGL/qopenglwindow_test.py +++ b/sources/pyside6/tests/QtOpenGL/qopenglwindow_test.py @@ -16,7 +16,7 @@ from helper.usesqapplication import UsesQApplication from PySide6.QtCore import QSize, QTimer, Qt from PySide6.QtGui import (QColor, QGuiApplication, QImage, QOpenGLContext, - QSurfaceFormat) + QSurfaceFormat) from PySide6.QtOpenGL import QOpenGLTexture, QOpenGLWindow diff --git a/sources/pyside6/tests/QtQml/bug_451.py b/sources/pyside6/tests/QtQml/bug_451.py index 299864ae..d81a99d9 100644 --- a/sources/pyside6/tests/QtQml/bug_451.py +++ b/sources/pyside6/tests/QtQml/bug_451.py @@ -28,6 +28,7 @@ from PySide6.QtQml import QmlElement QML_IMPORT_NAME = "test.PythonObject" QML_IMPORT_MAJOR_VERSION = 1 + @QmlElement class PythonObject(QObject): def __init__(self): diff --git a/sources/pyside6/tests/QtQml/bug_456.py b/sources/pyside6/tests/QtQml/bug_456.py index 7148102c..7743ee3f 100644 --- a/sources/pyside6/tests/QtQml/bug_456.py +++ b/sources/pyside6/tests/QtQml/bug_456.py @@ -19,6 +19,7 @@ from PySide6.QtQml import QmlElement QML_IMPORT_NAME = "test.RotateValue" QML_IMPORT_MAJOR_VERSION = 1 + @QmlElement class RotateValue(QObject): def __init__(self): diff --git a/sources/pyside6/tests/QtQml/bug_557.py b/sources/pyside6/tests/QtQml/bug_557.py index cc06a487..eb43973f 100644 --- a/sources/pyside6/tests/QtQml/bug_557.py +++ b/sources/pyside6/tests/QtQml/bug_557.py @@ -23,6 +23,6 @@ component = QQmlComponent(engine) # This should segfault if the QDeclarativeComponent has not QQmlEngine file = Path(__file__).resolve().parent / 'foo.qml' -assert(not file.is_file()) +assert (not file.is_file()) component.loadUrl(QUrl.fromLocalFile(file)) diff --git a/sources/pyside6/tests/QtQml/bug_726.py b/sources/pyside6/tests/QtQml/bug_726.py index 7ddc8e93..56c1e70f 100644 --- a/sources/pyside6/tests/QtQml/bug_726.py +++ b/sources/pyside6/tests/QtQml/bug_726.py @@ -18,6 +18,8 @@ from PySide6.QtQml import QmlElement QML_IMPORT_NAME = "test.ProxyObject" QML_IMPORT_MAJOR_VERSION = 1 + + @QmlElement class ProxyObject(QObject): def __init__(self): diff --git a/sources/pyside6/tests/QtQml/bug_814.py b/sources/pyside6/tests/QtQml/bug_814.py index 9db6488e..0e7858b6 100644 --- a/sources/pyside6/tests/QtQml/bug_814.py +++ b/sources/pyside6/tests/QtQml/bug_814.py @@ -29,13 +29,14 @@ from PySide6.QtQml import QmlElement QML_IMPORT_NAME = "test.ListModel" QML_IMPORT_MAJOR_VERSION = 1 + @QmlElement class ListModel(QAbstractListModel): def __init__(self): super().__init__() def roleNames(self): - return { Qt.DisplayRole: b'pysideModelData' } + return {Qt.DisplayRole: b'pysideModelData'} def rowCount(self, parent=QModelIndex()): return 3 diff --git a/sources/pyside6/tests/QtQml/bug_825.py b/sources/pyside6/tests/QtQml/bug_825.py index 1d9b82d1..a8bd304e 100644 --- a/sources/pyside6/tests/QtQml/bug_825.py +++ b/sources/pyside6/tests/QtQml/bug_825.py @@ -33,6 +33,7 @@ class MetaA(type): class A(object, metaclass=MetaA): pass + MetaB = type(QQuickPaintedItem) B = QQuickPaintedItem diff --git a/sources/pyside6/tests/QtQml/bug_926.py b/sources/pyside6/tests/QtQml/bug_926.py index c4853b9a..085e9a68 100644 --- a/sources/pyside6/tests/QtQml/bug_926.py +++ b/sources/pyside6/tests/QtQml/bug_926.py @@ -41,7 +41,7 @@ class MyClass (QObject): class TestBug926 (unittest.TestCase): def testIt(self): app = QGuiApplication([]) - qmlRegisterType(MyClass,'Example', 1, 0, 'MyClass') + qmlRegisterType(MyClass, 'Example', 1, 0, 'MyClass') view = QQuickView() file = Path(__file__).resolve().parent / 'bug_926.qml' self.assertTrue(file.is_file()) diff --git a/sources/pyside6/tests/QtQml/bug_995.py b/sources/pyside6/tests/QtQml/bug_995.py index 745dd7da..868c584e 100644 --- a/sources/pyside6/tests/QtQml/bug_995.py +++ b/sources/pyside6/tests/QtQml/bug_995.py @@ -19,7 +19,7 @@ from PySide6.QtQuick import QQuickView app = QGuiApplication([]) file = Path(__file__).resolve().parent / 'bug_995.qml' -assert(file.is_file()) +assert (file.is_file()) view = QQuickView(QUrl.fromLocalFile(file)) view.show() view.resize(200, 200) diff --git a/sources/pyside6/tests/QtQml/connect_python_qml.py b/sources/pyside6/tests/QtQml/connect_python_qml.py index f0df1c83..2e60aec4 100644 --- a/sources/pyside6/tests/QtQml/connect_python_qml.py +++ b/sources/pyside6/tests/QtQml/connect_python_qml.py @@ -42,7 +42,8 @@ class TestConnectionWithInvalidSignature(TimedQGuiApplication): root = view.rootObject() self.assertTrue(root, quickview_errorstring(view)) button = root.findChild(QObject, "buttonMouseArea") - self.assertRaises(TypeError, QObject.connect, [button,SIGNAL('entered()'), self.onButtonFailClicked]) + self.assertRaises(TypeError, QObject.connect, + [button, SIGNAL('entered()'), self.onButtonFailClicked]) button.entered.connect(self.onButtonClicked) button.entered.emit() view.show() diff --git a/sources/pyside6/tests/QtQml/qquickview_test.py b/sources/pyside6/tests/QtQml/qquickview_test.py index 5cf5550e..22650966 100644 --- a/sources/pyside6/tests/QtQml/qquickview_test.py +++ b/sources/pyside6/tests/QtQml/qquickview_test.py @@ -22,6 +22,7 @@ from PySide6.QtQuick import QQuickView class MyObject(QObject): titleChanged = Signal() + def __init__(self, text, parent=None): QObject.__init__(self, parent) self._text = text diff --git a/sources/pyside6/tests/QtQml/registersingletontype.py b/sources/pyside6/tests/QtQml/registersingletontype.py index f2e318e4..492217fd 100644 --- a/sources/pyside6/tests/QtQml/registersingletontype.py +++ b/sources/pyside6/tests/QtQml/registersingletontype.py @@ -49,6 +49,7 @@ def singletonQJSValueCallback(engine): QML_IMPORT_NAME = "Singletons" QML_IMPORT_MAJOR_VERSION = 1 + @QmlElement @QmlSingleton class DecoratedSingletonQObject(QObject): diff --git a/sources/pyside6/tests/QtQml/signal_arguments.py b/sources/pyside6/tests/QtQml/signal_arguments.py index 304c83ca..f5b0f8bd 100644 --- a/sources/pyside6/tests/QtQml/signal_arguments.py +++ b/sources/pyside6/tests/QtQml/signal_arguments.py @@ -20,6 +20,7 @@ from PySide6.QtQml import QmlElement QML_IMPORT_NAME = "test.Obj" QML_IMPORT_MAJOR_VERSION = 1 + @QmlElement class Obj(QObject): def __init__(self): diff --git a/sources/pyside6/tests/QtUiTools/bug_1060.py b/sources/pyside6/tests/QtUiTools/bug_1060.py index c247cf9f..52624a2a 100644 --- a/sources/pyside6/tests/QtUiTools/bug_1060.py +++ b/sources/pyside6/tests/QtUiTools/bug_1060.py @@ -27,6 +27,6 @@ if __name__ == "__main__": app = QApplication([]) file = Path(__file__).resolve().parent / 'bug_1060.ui' - assert(file.is_file()) + assert (file.is_file()) ui = MyQUiLoader().load(file) ui.show() diff --git a/sources/pyside6/tests/QtUiTools/bug_552.py b/sources/pyside6/tests/QtUiTools/bug_552.py index ce040f4f..06a6b72a 100644 --- a/sources/pyside6/tests/QtUiTools/bug_552.py +++ b/sources/pyside6/tests/QtUiTools/bug_552.py @@ -21,7 +21,7 @@ class View_1(QWidget): super().__init__() loader = QUiLoader() file = Path(__file__).resolve().parent / 'bug_552.ui' - assert(file.is_file()) + assert (file.is_file()) widget = loader.load(os.fspath(file), self) self.children = [] for child in widget.findChildren(QObject, None): diff --git a/sources/pyside6/tests/QtUiTools/bug_797.py b/sources/pyside6/tests/QtUiTools/bug_797.py index b87ca129..3dae7afb 100644 --- a/sources/pyside6/tests/QtUiTools/bug_797.py +++ b/sources/pyside6/tests/QtUiTools/bug_797.py @@ -18,7 +18,7 @@ from PySide6.QtWidgets import QApplication, QWidget app = QApplication([]) loader = QUiLoader() file = Path(__file__).resolve().parent / 'bug_552.ui' -assert(file.is_file()) +assert (file.is_file()) file = QFile(file) w = QWidget() # An exception can't be thrown diff --git a/sources/pyside6/tests/QtUiTools/bug_958.py b/sources/pyside6/tests/QtUiTools/bug_958.py index 69f57b03..2f8dfab6 100644 --- a/sources/pyside6/tests/QtUiTools/bug_958.py +++ b/sources/pyside6/tests/QtUiTools/bug_958.py @@ -23,7 +23,7 @@ class Gui_Qt(QMainWindow): # this used to cause a segfault because the old inject code used to destroy the parent layout file = Path(__file__).resolve().parent / 'bug_958.ui' - assert(file.is_file()) + assert (file.is_file()) self._cw = lLoader.load(file, self) self.setCentralWidget(self._cw) diff --git a/sources/pyside6/tests/QtWidgets/bug_400.py b/sources/pyside6/tests/QtWidgets/bug_400.py index 6e99fbd0..79dfa49f 100644 --- a/sources/pyside6/tests/QtWidgets/bug_400.py +++ b/sources/pyside6/tests/QtWidgets/bug_400.py @@ -27,7 +27,7 @@ class BugTest(UsesQApplication): treeWidget.insertTopLevelItems(0, items) _iter = QTreeWidgetItemIterator(treeWidget) index = 0 - while(_iter.value()): + while (_iter.value()): item = _iter.value() self.assertTrue(item is items[index]) index += 1 diff --git a/sources/pyside6/tests/QtWidgets/bug_667.py b/sources/pyside6/tests/QtWidgets/bug_667.py index 414b3d82..be6700d8 100644 --- a/sources/pyside6/tests/QtWidgets/bug_667.py +++ b/sources/pyside6/tests/QtWidgets/bug_667.py @@ -17,7 +17,7 @@ from PySide6.QtWidgets import QApplication, QGraphicsView, QGraphicsScene, QGrap class Ball(QGraphicsEllipseItem): def __init__(self, d, parent=None): super().__init__(0, 0, d, d, parent) - self.vel = QPointF(0, 0) #commenting this out prevents the crash + self.vel = QPointF(0, 0) # commenting this out prevents the crash class Foo(QGraphicsView): diff --git a/sources/pyside6/tests/QtWidgets/bug_854.py b/sources/pyside6/tests/QtWidgets/bug_854.py index f99bf6b0..1dbfd4b3 100644 --- a/sources/pyside6/tests/QtWidgets/bug_854.py +++ b/sources/pyside6/tests/QtWidgets/bug_854.py @@ -10,8 +10,8 @@ sys.path.append(os.fspath(Path(__file__).resolve().parents[1])) from init_paths import init_test_paths init_test_paths(False) -from PySide6.QtWidgets import QTableView, QVBoxLayout, QApplication -from PySide6.QtCore import QAbstractItemModel +from PySide6.QtWidgets import QTableView, QApplication +from PySide6.QtCore import QAbstractItemModel, QModelIndex from helper.usesqapplication import UsesQApplication diff --git a/sources/pyside6/tests/QtWidgets/paint_event_test.py b/sources/pyside6/tests/QtWidgets/paint_event_test.py index f63d8a56..d995c2a3 100644 --- a/sources/pyside6/tests/QtWidgets/paint_event_test.py +++ b/sources/pyside6/tests/QtWidgets/paint_event_test.py @@ -26,7 +26,7 @@ class MyWidget(QWidget): def __init__(self, app): # Creates a new widget - assert(app) + assert (app) super().__init__() self.app = app diff --git a/sources/pyside6/tests/QtWidgets/qlabel_test.py b/sources/pyside6/tests/QtWidgets/qlabel_test.py index 4ec8cb98..aba90b7b 100644 --- a/sources/pyside6/tests/QtWidgets/qlabel_test.py +++ b/sources/pyside6/tests/QtWidgets/qlabel_test.py @@ -64,7 +64,7 @@ class QLabelTest(UsesQApplication): # address that our QPixmap p1 because it was deleted # using `del ret_p` self.assertTrue(all(Shiboken.getCppPointer(o) != ret_p_addr - for o in Shiboken.getAllValidWrappers())) + for o in Shiboken.getAllValidWrappers())) # Test for PYSIDE-1673, QObject.property() returning a QFlags<> property. def testQObjectProperty(self): diff --git a/sources/pyside6/tests/QtWidgets/qmenu_test.py b/sources/pyside6/tests/QtWidgets/qmenu_test.py index 8f93c6f9..a6976a63 100644 --- a/sources/pyside6/tests/QtWidgets/qmenu_test.py +++ b/sources/pyside6/tests/QtWidgets/qmenu_test.py @@ -37,11 +37,11 @@ class QMenuAddAction(UsesQApplication): def testAddActionKeySequenceCallable(self): # bug #228 action = self.menu.addAction(self.app.tr('aaa'), lambda: 1, - QKeySequence(self.app.tr('Ctrl+O'))) + QKeySequence(self.app.tr('Ctrl+O'))) def testAddActionKeySequenceSlot(self): action = self.menu.addAction('Quit', self.app, SLOT('quit()'), - QKeySequence('Ctrl+O')) + QKeySequence('Ctrl+O')) class QMenuAddActionWithIcon(UsesQApplication): @@ -65,11 +65,11 @@ class QMenuAddActionWithIcon(UsesQApplication): def testAddActionKeySequenceCallable(self): # bug #228 action = self.menu.addAction(self.icon, self.app.tr('aaa'), lambda: 1, - QKeySequence(self.app.tr('Ctrl+O'))) + QKeySequence(self.app.tr('Ctrl+O'))) def testAddActionKeySequenceSlot(self): action = self.menu.addAction(self.icon, 'Quit', self.app, SLOT('quit()'), - QKeySequence('Ctrl+O')) + QKeySequence('Ctrl+O')) if __name__ == '__main__': diff --git a/sources/pyside6/tests/QtWidgets/qstyle_test.py b/sources/pyside6/tests/QtWidgets/qstyle_test.py index 9b8872f8..5a9b29dc 100644 --- a/sources/pyside6/tests/QtWidgets/qstyle_test.py +++ b/sources/pyside6/tests/QtWidgets/qstyle_test.py @@ -15,7 +15,7 @@ from helper.usesqapplication import UsesQApplication from PySide6.QtGui import QWindow from PySide6.QtWidgets import (QApplication, QFontComboBox, QLabel, QProxyStyle, - QStyleFactory, QWidget) + QStyleFactory, QWidget) class ProxyStyle(QProxyStyle): diff --git a/sources/pyside6/tests/QtWidgets/qtreeview_test.py b/sources/pyside6/tests/QtWidgets/qtreeview_test.py index 6e046e9d..fd535bfa 100644 --- a/sources/pyside6/tests/QtWidgets/qtreeview_test.py +++ b/sources/pyside6/tests/QtWidgets/qtreeview_test.py @@ -13,7 +13,7 @@ init_test_paths(False) from PySide6.QtGui import QStandardItemModel from PySide6.QtWidgets import (QWidget, QTreeView, QVBoxLayout, - QStyledItemDelegate, QHeaderView) + QStyledItemDelegate, QHeaderView) from PySide6.QtCore import Qt from helper.usesqapplication import UsesQApplication diff --git a/sources/pyside6/tests/QtWidgets/qvariant_test.py b/sources/pyside6/tests/QtWidgets/qvariant_test.py index edb4855c..fe026630 100644 --- a/sources/pyside6/tests/QtWidgets/qvariant_test.py +++ b/sources/pyside6/tests/QtWidgets/qvariant_test.py @@ -13,7 +13,7 @@ init_test_paths(False) from PySide6.QtCore import Qt, QObject from PySide6.QtWidgets import (QComboBox, QGraphicsScene, - QGraphicsRectItem) + QGraphicsRectItem) from helper.usesqapplication import UsesQApplication @@ -114,8 +114,8 @@ class QVariantConversionTest(UsesQApplication): def testContainerTypes(self): #list - self.obj.setProperty("test", [1,2,3]) - self.assertEqual(self.obj.property("test"), [1,2,3]) + self.obj.setProperty("test", [1, 2, 3]) + self.assertEqual(self.obj.property("test"), [1, 2, 3]) self.assertTrue(isinstance(self.obj.property("test"), list)) #dict self.obj.setProperty("test", {1: "one"}) diff --git a/sources/pyside6/tests/pysidetest/constructor_properties_test.py b/sources/pyside6/tests/pysidetest/constructor_properties_test.py index 9f1a9d43..ec6e3982 100644 --- a/sources/pyside6/tests/pysidetest/constructor_properties_test.py +++ b/sources/pyside6/tests/pysidetest/constructor_properties_test.py @@ -36,7 +36,7 @@ class ConstructorPropertiesTest(unittest.TestCase): def testCallConstructor(self): label = QLabel( frameStyle=QFrame.Panel | QFrame.Sunken, # QFrame attr, no property - lineWidth = 2, # QFrame property + lineWidth=2, # QFrame property text="first line\nsecond line", # QLabel property alignment=Qt.AlignBottom | Qt.AlignRight # QLabel property ) @@ -52,13 +52,13 @@ class ConstructorPropertiesTest(unittest.TestCase): label = QLabel( frame_style=QFrame.Panel | QFrame.Sunken, # QFrame attr, no property - line_width = 2, # QFrame property + line_width=2, # QFrame property text="first line\nsecond line", # QLabel property alignment=Qt.AlignBottom | Qt.AlignRight # QLabel property ) self.assertEqual(label.line_width(), 2) self.assertRaises(AttributeError, lambda: QLabel( - lineWidth = 2, # QFrame property + lineWidth=2, # QFrame property )) # PYSIDE-1705: The same with true_property @@ -68,13 +68,13 @@ class ConstructorPropertiesTest(unittest.TestCase): label = QLabel( frameStyle=QFrame.Panel | QFrame.Sunken, # QFrame attr, no property - lineWidth = 2, # QFrame property + lineWidth=2, # QFrame property text="first line\nsecond line", # QLabel property alignment=Qt.AlignBottom | Qt.AlignRight # QLabel property ) self.assertEqual(label.lineWidth, 2) self.assertRaises(AttributeError, lambda: QLabel( - line_width = 2, # QFrame property + line_width=2, # QFrame property )) # PYSIDE-1705: The same with snake_case and true_property @@ -84,13 +84,13 @@ class ConstructorPropertiesTest(unittest.TestCase): label = QLabel( frame_style=QFrame.Panel | QFrame.Sunken, # QFrame attr, no property - line_width = 2, # QFrame property + line_width=2, # QFrame property text="first line\nsecond line", # QLabel property alignment=Qt.AlignBottom | Qt.AlignRight # QLabel property ) self.assertEqual(label.line_width, 2) self.assertRaises(AttributeError, lambda: QLabel( - lineWidth = 2, # QFrame property + lineWidth=2, # QFrame property )) diff --git a/sources/pyside6/tests/pysidetest/container_test.py b/sources/pyside6/tests/pysidetest/container_test.py index 68c3638c..c83e1f26 100644 --- a/sources/pyside6/tests/pysidetest/container_test.py +++ b/sources/pyside6/tests/pysidetest/container_test.py @@ -44,13 +44,13 @@ class ContainerTestTest(unittest.TestCase): self.assertEqual(sort_values(m2), EXPECTED_DICT) def testList(self): - l1 = ContainerTest.createList(); + l1 = ContainerTest.createList() self.assertEqual(l1, EXPECTED_LIST) l2 = ContainerTest.passThroughList(l1) self.assertEqual(l2, EXPECTED_LIST) def testSet(self): - s1 = ContainerTest.createSet(); # Order is not predictable + s1 = ContainerTest.createSet() # Order is not predictable s2 = ContainerTest.passThroughSet(s1) self.assertEqual(sorted(list(s1)), sorted(list(s2))) diff --git a/sources/pyside6/tests/pysidetest/enum_test.py b/sources/pyside6/tests/pysidetest/enum_test.py index 189ef60c..83283453 100644 --- a/sources/pyside6/tests/pysidetest/enum_test.py +++ b/sources/pyside6/tests/pysidetest/enum_test.py @@ -15,6 +15,7 @@ from testbinding import Enum1, TestObjectWithoutNamespace import dis + class ListConnectionTest(unittest.TestCase): def testEnumVisibility(self): @@ -45,6 +46,7 @@ class ListConnectionTest(unittest.TestCase): self.assertFalse(Qt.AlignBottom < Qt.AlignHCenter) self.assertTrue(Qt.AlignBottom > Qt.AlignHCenter) + # PYSIDE-1735: We are testing that opcodes do what they are supposed to do. # This is needed in the PyEnum forgiveness mode where we need # to introspect the code if an Enum was called with no args. @@ -69,6 +71,7 @@ class InvestigateOpcodesTest(unittest.TestCase): return sorted(res, key=lambda x: (x[1], x[0])) _sin = sys.implementation.name + @unittest.skipIf(hasattr(sys.flags, "nogil"), f"{_sin} has different opcodes") def testByteCode(self): import dis @@ -134,7 +137,7 @@ class InvestigateOpcodesTest(unittest.TestCase): for _ in range(times): f() - code_quicken(self.probe_function2, QUICKENING_WARMUP_DELAY-1) + code_quicken(self.probe_function2, QUICKENING_WARMUP_DELAY - 1) self.assertEqual(self.read_code(self.probe_function2, adaptive=True), result_2) self.assertEqual(self.get_sizes(self.probe_function2, adaptive=True), sizes_2) @@ -174,7 +177,6 @@ class InvestigateOpcodesTest(unittest.TestCase): ('STORE_FAST', 125, 1), ('RETURN_CONST', 121, 0)] - self.assertEqual(self.read_code(self.probe_function1), result_1) self.assertEqual(self.read_code(self.probe_function2), result_2) diff --git a/sources/pyside6/tests/pysidetest/homonymoussignalandmethod_test.py b/sources/pyside6/tests/pysidetest/homonymoussignalandmethod_test.py index 52c65e54..b58232a1 100644 --- a/sources/pyside6/tests/pysidetest/homonymoussignalandmethod_test.py +++ b/sources/pyside6/tests/pysidetest/homonymoussignalandmethod_test.py @@ -99,8 +99,8 @@ class HomonymousMultipleInheritanceTest(unittest.TestCase): def testHomonymousMultipleInheritance(self): c = C() - self.assertEqual(c.method(), "M::method") # okay - self.assertEqual(c.signal(), "M::signal") # problem on PySide6 6.2.2 + self.assertEqual(c.method(), "M::method") # okay + self.assertEqual(c.signal(), "M::signal") # problem on PySide6 6.2.2 self.assertEqual(type(c.signal), SignalInstance) diff --git a/sources/pyside6/tests/pysidetest/multiple_inheritance_test.py b/sources/pyside6/tests/pysidetest/multiple_inheritance_test.py index 033a065a..fe8e14f9 100644 --- a/sources/pyside6/tests/pysidetest/multiple_inheritance_test.py +++ b/sources/pyside6/tests/pysidetest/multiple_inheritance_test.py @@ -19,6 +19,7 @@ def xprint(*args, **kw): if "-v" in sys.argv: print(*args, **kw) + # This is the original testcase of PYSIDE-1564 class Age(object): def __init__(self, age=0, **kwds): @@ -26,6 +27,7 @@ class Age(object): self.age = age + class Person(QtCore.QObject, Age): def __init__(self, name, **kwds): super().__init__(**kwds) @@ -43,6 +45,7 @@ class OriginalMultipleInheritanceTest(unittest.TestCase): # More tests follow: + # mro ('C', 'A', 'QObject', 'Object', 'B', 'object') class A(QtCore.QObject): def __init__(self, anna=77, **kw): @@ -50,6 +53,7 @@ class A(QtCore.QObject): super().__init__(**kw) xprint('A: after init') + class B: def __init__(self, otto=6, age=7, **kw): xprint(f'B: before init kw = {kw}') @@ -59,12 +63,14 @@ class B: self.age = age xprint('B: after init') + class C(A, B): def __init__(self, **kw): xprint(f'C: before init kw = {kw}') super().__init__(**kw) xprint('C: after init') + # mro ('F', 'D', 'QCursor', 'E', 'QLabel', 'QFrame', 'QWidget', 'QObject', 'QPaintDevice', 'Object', 'object') class D(QtGui.QCursor): def __init__(self, anna=77, **kw): @@ -72,6 +78,7 @@ class D(QtGui.QCursor): super().__init__(**kw) xprint('D: after init') + class E: def __init__(self, age=7, **kw): xprint(f'E: before init kw = {kw}') @@ -79,17 +86,20 @@ class E: self.age = age xprint('E: after init') + class F(D, E, QtWidgets.QLabel): def __init__(self, **kw): xprint(f'F: before init kw = {kw}') super().__init__(**kw) xprint('F: after init') + # mro ('I', 'G', 'QTextDocument', 'H', 'QLabel', 'QFrame', 'QWidget', 'QObject', 'QPaintDevice', 'Object', 'object') # Similar, but this time we want to reach `H` without support from `super`. class G(QtGui.QTextDocument): pass + class H: def __init__(self, age=7, **kw): xprint(f'H: before init kw = {kw}') @@ -97,6 +107,7 @@ class H: self.age = age xprint('H: after init') + class I(G, H, QtWidgets.QLabel): pass @@ -108,6 +119,7 @@ class Ui_X_MainWindow(object): # Emulating uic MainWindow.resize(400, 300) self.lbl = QLabel(self) + class MainWindow(QMainWindow, Ui_X_MainWindow): def __init__(self, parent=None): super().__init__(parent) @@ -122,7 +134,7 @@ class AdditionalMultipleInheritanceTest(UsesQApplication): self.assertEqual(res.age, 7) xprint() with self.assertRaises(AssertionError): - res=C(killme=42) + res = C(killme=42) xprint() def testDEF(self): diff --git a/sources/pyside6/tests/pysidetest/pyenum_relax_options_test.py b/sources/pyside6/tests/pysidetest/pyenum_relax_options_test.py index 5ccbfb92..625f9cdc 100644 --- a/sources/pyside6/tests/pysidetest/pyenum_relax_options_test.py +++ b/sources/pyside6/tests/pysidetest/pyenum_relax_options_test.py @@ -49,6 +49,7 @@ def runtest(program): finally: os.unlink(fp.name) + def testprog2(option): return runtest(dedent(f""" sys.pyside6_option_python_enum = {option} @@ -57,6 +58,7 @@ def testprog2(option): assert(issubclass(QtCore.Qt.DateFormat, IntEnum)) """)) + def testprog4(option): return runtest(dedent(f""" sys.pyside6_option_python_enum = {option} @@ -64,6 +66,7 @@ def testprog4(option): QtCore.QtDebugMsg """)) + def testprog8_16(option): # this test needs flag 16, or the effect would be hidden by forgiving mode return runtest(dedent(f""" @@ -72,6 +75,7 @@ def testprog8_16(option): QtCore.Qt.AlignTop """)) + def testprog32(option): return runtest(dedent(f""" sys.pyside6_option_python_enum = {option} @@ -79,6 +83,7 @@ def testprog32(option): QtCore.Qt.Alignment """)) + def testprog64(option): return runtest(dedent(f""" sys.pyside6_option_python_enum = {option} @@ -86,6 +91,7 @@ def testprog64(option): QtCore.Qt.AlignmentFlag() """)) + def testprog128(option): return runtest(dedent(f""" sys.pyside6_option_python_enum = {option} @@ -110,8 +116,8 @@ class TestPyEnumRelaxOption(unittest.TestCase): self.assertTrue(testprog4(12)) def test_localDefault(self): - self.assertTrue(testprog8_16(8+16)) - self.assertFalse(testprog8_16(0+16)) + self.assertTrue(testprog8_16(8 + 16)) + self.assertFalse(testprog8_16(0 + 16)) def test_fakeRenames(self): self.assertTrue(testprog32(1)) diff --git a/sources/pyside6/tests/pysidetest/signalinstance_equality_test.py b/sources/pyside6/tests/pysidetest/signalinstance_equality_test.py index d09d63c0..5faaa38d 100644 --- a/sources/pyside6/tests/pysidetest/signalinstance_equality_test.py +++ b/sources/pyside6/tests/pysidetest/signalinstance_equality_test.py @@ -40,6 +40,7 @@ class TestSignalInstance(unittest.TestCase): def test_custom_inherited_signal_instances_are_equal(self): o = D() self.assertTrue(o.custom_signal == o.custom_signal) + # additional tests of old errors from 2010 or so def test_uninitialized_SignalInstance(self): # This will no longer crash @@ -51,6 +52,7 @@ class TestSignalInstance(unittest.TestCase): with self.assertRaises(RuntimeError): SignalInstance().emit() + class MyWidget(QSlider): valueChanged = Signal(tuple) diff --git a/sources/pyside6/tests/pysidetest/snake_case_sub.py b/sources/pyside6/tests/pysidetest/snake_case_sub.py index 5056d50b..4a482c35 100644 --- a/sources/pyside6/tests/pysidetest/snake_case_sub.py +++ b/sources/pyside6/tests/pysidetest/snake_case_sub.py @@ -16,6 +16,7 @@ PYSIDE-2029: Tests that snake_case is isolated from imported modules from PySide6.QtWidgets import QWidget + def test_no_snake_case(): print(__name__) widget = QWidget() diff --git a/sources/pyside6/tests/pysidetest/snake_case_test.py b/sources/pyside6/tests/pysidetest/snake_case_test.py index aaa3d3f2..14e03577 100644 --- a/sources/pyside6/tests/pysidetest/snake_case_test.py +++ b/sources/pyside6/tests/pysidetest/snake_case_test.py @@ -13,14 +13,17 @@ init_test_paths(False) """ PYSIDE-2029: Tests that snake_case is isolated from imported modules """ +is_pypy = hasattr(sys, "pypy_version_info") from PySide6.QtCore import QSize from PySide6.QtWidgets import QWidget, QSpinBox -from __feature__ import snake_case +if not is_pypy: + from __feature__ import snake_case from helper.usesqapplication import UsesQApplication import snake_case_sub +@unittest.skipIf(is_pypy, "__feature__ cannot yet be used with PyPy") class SnakeCaseNoPropagateTest(UsesQApplication): def testSnakeCase(self): @@ -30,5 +33,6 @@ class SnakeCaseNoPropagateTest(UsesQApplication): snake_case_sub.test_no_snake_case() + if __name__ == '__main__': unittest.main() diff --git a/sources/pyside6/tests/pysidetest/true_property_test.py b/sources/pyside6/tests/pysidetest/true_property_test.py index 0b9a909b..62f6505d 100644 --- a/sources/pyside6/tests/pysidetest/true_property_test.py +++ b/sources/pyside6/tests/pysidetest/true_property_test.py @@ -13,13 +13,16 @@ init_test_paths(False) """ PYSIDE-2042: Tests true_property with inheritance """ +is_pypy = hasattr(sys, "pypy_version_info") from PySide6.QtCore import QSize from PySide6.QtWidgets import QWidget, QSpinBox -from __feature__ import true_property +if not is_pypy: + from __feature__ import true_property from helper.usesqapplication import UsesQApplication +@unittest.skipIf(is_pypy, "__feature__ cannot yet be used with PyPy") class TruePropertyInheritanceTest(UsesQApplication): def testTrueProperty(self): @@ -43,6 +46,7 @@ class TruePropertyInheritanceTest(UsesQApplication): self.assertTrue(hasattr(widget, "setVisible")) self.assertEqual(widget.isVisible, QWidget.visible.fget) self.assertEqual(widget.setVisible, QWidget.visible.fset) + # This works with inheritance as well: class SubClass(QWidget): pass diff --git a/sources/pyside6/tests/registry/scrape_testresults.py b/sources/pyside6/tests/registry/scrape_testresults.py index a6e3b048..b7b6b58a 100644 --- a/sources/pyside6/tests/registry/scrape_testresults.py +++ b/sources/pyside6/tests/registry/scrape_testresults.py @@ -212,7 +212,7 @@ def handle_suburl(idx, n, url, level): print(os.getpid(), test_name) response = read_url(sub_url) txt = response.text if response else '' - if "BEGIN_FILE" in txt and not "'BEGIN_FILE'" in txt: + if "BEGIN_FILE" in txt and "'BEGIN_FILE'" not in txt: # find the text, but not a traceback with that text print(os.getpid(), test_name, "FOUND!") write_data(test_name, response.text) diff --git a/sources/pyside6/tests/registry/util.py b/sources/pyside6/tests/registry/util.py index 0359d28c..a1c5e05e 100644 --- a/sources/pyside6/tests/registry/util.py +++ b/sources/pyside6/tests/registry/util.py @@ -26,8 +26,6 @@ def isolate_warnings(): if mod and hasattr(mod, warn_name): save_warnings[name] = mod.__dict__[warn_name] delattr(mod, warn_name) - else: - save_warnings[name] = None yield for name, warn in save_warnings.items(): mod = sys.modules[name] diff --git a/sources/pyside6/tests/signals/invalid_callback_test.py b/sources/pyside6/tests/signals/invalid_callback_test.py index 0d7e574e..b5252870 100644 --- a/sources/pyside6/tests/signals/invalid_callback_test.py +++ b/sources/pyside6/tests/signals/invalid_callback_test.py @@ -35,7 +35,7 @@ class InvalidCallback(unittest.TestCase): def testIntegerCb(self): # Test passing an int as callback to QObject.connect self.assertRaises(TypeError, QObject.connect, self.obj, - SIGNAL('destroyed()'), 42) + SIGNAL('destroyed()'), 42) if __name__ == '__main__': diff --git a/sources/pyside6/tests/signals/qobject_sender_test.py b/sources/pyside6/tests/signals/qobject_sender_test.py index a7cda69f..485584ec 100644 --- a/sources/pyside6/tests/signals/qobject_sender_test.py +++ b/sources/pyside6/tests/signals/qobject_sender_test.py @@ -73,6 +73,12 @@ class ObjectSenderWithQAppTest(UsesQApplication): self.app.exec() self.assertTrue(isinstance(recv.the_sender, QObject)) + def testSenderCppSignalSingleShotTimerWithContext(self): + recv = Receiver() + QTimer.singleShot(10, recv, recv.callback) + self.app.exec() + self.assertTrue(isinstance(recv.the_sender, QObject)) + def testSenderCppSignalWithPythonExtendedClass(self): sender = ExtQTimer() recv = Receiver() @@ -105,4 +111,3 @@ class ObjectSenderWithQAppCheckOnReceiverTest(UsesQApplication): if __name__ == '__main__': unittest.main() - diff --git a/sources/pyside6/tests/tools/list-class-hierarchy.py b/sources/pyside6/tests/tools/list-class-hierarchy.py index f6c20ee2..b773b7c5 100644 --- a/sources/pyside6/tests/tools/list-class-hierarchy.py +++ b/sources/pyside6/tests/tools/list-class-hierarchy.py @@ -19,8 +19,7 @@ from inspect import isclass ignore = ["staticMetaObject", "pyqtConfigure", "registerUserData", - "thread", - ] + "thread"] def recurse_into(el, obj): @@ -47,7 +46,7 @@ def recurse_into(el, obj): return symbols -if __name__=='__main__': +if __name__ == '__main__': modules = [ 'QtCore', 'QtGui', 'QtHelp', diff --git a/sources/pyside6/tests/tools/pyside6-android-deploy/test_pyside6_android_deploy.py b/sources/pyside6/tests/tools/pyside6-android-deploy/test_pyside6_android_deploy.py index 5801c6fd..81c2c9a4 100644 --- a/sources/pyside6/tests/tools/pyside6-android-deploy/test_pyside6_android_deploy.py +++ b/sources/pyside6/tests/tools/pyside6-android-deploy/test_pyside6_android_deploy.py @@ -1,45 +1,46 @@ # Copyright (C) 2023 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only -import unittest -import tempfile +import importlib +import os +import re import shutil import sys -import os -import importlib +import tempfile +import unittest +import subprocess from pathlib import Path -from configparser import ConfigParser -from unittest.mock import patch from unittest import mock +from unittest.mock import patch - -class ConfigFile: - def __init__(self, config_file: Path) -> None: - self.config_file = config_file - self.parser = ConfigParser(comment_prefixes="/", allow_no_value=True) - self.parser.read(self.config_file) - - def get_value(self, section: str, key: str): - return str(self.parser.get(section, key)) +sys.path.append(os.fspath(Path(__file__).resolve().parents[2])) +from init_paths import init_test_paths +init_test_paths(False) -class TestPySide6AndroidDeploy(unittest.TestCase): +class DeployTestBase(unittest.TestCase): @classmethod def setUpClass(cls): cls.pyside_root = Path(__file__).parents[5].resolve() cls.example_root = cls.pyside_root / "examples" - example_widget_application = cls.example_root / "gui" / "analogclock" cls.temp_dir = tempfile.mkdtemp() - cls.temp_example = Path( - shutil.copytree(example_widget_application, Path(cls.temp_dir) / "analogclock") - ).resolve() cls.current_dir = Path.cwd() - cls.pyside_wheel = Path("tmp/PySide6-6.5.0a1-6.5.0-cp37-abi3-android_x86_64.whl") - cls.shiboken_wheel = Path("tmp/shiboken6-6.5.0a1-6.5.0-cp37-abi3-android_x86_64.whl") - cls.ndk_path = Path("tmp/android_sdk/ndk/25.2.9519653") - cls.sdk_path = Path("tmp/android_sdk") - - sys.path.append(str(cls.pyside_root / "sources" / "pyside-tools")) + cls.pyside_wheel = Path("/tmp/PySide6-6.5.0a1-6.5.0-cp37-abi3-android_x86_64.whl") + cls.shiboken_wheel = Path("/tmp/shiboken6-6.5.0a1-6.5.0-cp37-abi3-android_x86_64.whl") + cls.ndk_path = Path("/tmp/android_sdk/ndk/25.2.9519653") + cls.sdk_path = Path("/tmp/android_sdk") + pyside_tools = cls.pyside_root / "sources" / "pyside-tools" + + # install extra python dependencies + android_requirements_file = pyside_tools / "requirements-android.txt" + with open(android_requirements_file, 'r', encoding='UTF-8') as file: + while line := file.readline(): + dependent_package = line.rstrip() + if not bool(importlib.util.find_spec(dependent_package)): + command = [sys.executable, "-m", "pip", "install", dependent_package] + subprocess.run(command) + + sys.path.append(str(pyside_tools)) cls.deploy_lib = importlib.import_module("deploy_lib") cls.android_deploy = importlib.import_module("android_deploy") sys.modules["android_deploy"] = cls.android_deploy @@ -50,127 +51,213 @@ class TestPySide6AndroidDeploy(unittest.TestCase): # print no outputs to stdout sys.stdout = mock.MagicMock() + def tearDown(self) -> None: + super().tearDown() + os.chdir(self.current_dir) + + @classmethod + def tearDownClass(cls) -> None: + shutil.rmtree(Path(cls.temp_dir)) + + +@patch("deploy_lib.android.android_config.extract_and_copy_jar") +class TestPySide6AndroidDeployWidgets(DeployTestBase): + @classmethod + def setUpClass(cls): + super().setUpClass() + example_widget_application = cls.example_root / "gui" / "analogclock" + cls.temp_example = Path( + shutil.copytree(example_widget_application, Path(cls.temp_dir) / "analogclock") + ).resolve() + def setUp(self): os.chdir(self.temp_example) self.config_file = self.temp_example / "pysidedeploy.spec" + self.buildozer_config = self.temp_example / "buildozer.spec" - @patch("android_deploy.extract_and_copy_jar") - @patch("android_deploy.Wheel") - def test_dry_run(self, mock_jar, mock_wheel): - mock_wheel.version = "6.5.0a1" - - # test if dry_run works without errors + def test_dry_run(self, mock_extract_jar): self.android_deploy.main(name="android_app", shiboken_wheel=self.shiboken_wheel, - pyside_wheel=self.pyside_wheel, dry_run=True, force=True) - - self.assertEqual(mock_wheel.call_count, 1) - self.assertEqual(mock_jar.call_count, 1) - self.assertFalse(self.config_file.exists()) - - @patch("android_deploy.extract_and_copy_jar") - @patch("android_deploy.Wheel") - def test_config(self, mock_jar, mock_wheel): - ''' - Tests config options from the dynamically created buildozer.spec and pysidedeploy.spec - ''' - mock_wheel.version = "6.5.0a1" + pyside_wheel=self.pyside_wheel, ndk_path=self.ndk_path, + dry_run=True, force=True) + self.assertEqual(mock_extract_jar.call_count, 0) + + @patch("deploy_lib.android.buildozer.BuildozerConfig._BuildozerConfig__find_jars") + @patch("deploy_lib.android.android_config.AndroidConfig.recipes_exist") + @patch("deploy_lib.android.buildozer.BuildozerConfig." + "_BuildozerConfig__find_dependent_qt_modules") + @patch("deploy_lib.android.buildozer.find_qtlibs_in_wheel") + def test_config(self, mock_qtlibs, mock_extraqtmodules, mock_recipes_exist, mock_find_jars, + mock_extract_jar): + jar_dir = "tmp/jar/PySide6/jar" + mock_extract_jar.return_value = Path(jar_dir) + mock_qtlibs.return_value = self.pyside_wheel / "PySide6/Qt/lib" + mock_extraqtmodules.return_value = [] + mock_recipes_exist.return_value = True + jars, init_classes = ["/tmp/jar/PySide6/jar/Qt6Android.jar", + "/tmp/jar/PySide6/jar/Qt6AndroidBindings.jar"], [] + mock_find_jars.return_value = jars, init_classes self.android_deploy.main(name="android_app", shiboken_wheel=self.shiboken_wheel, - pyside_wheel=self.pyside_wheel, init=True, force=True) + pyside_wheel=self.pyside_wheel, ndk_path=self.ndk_path, + init=True, force=True, keep_deployment_files=True) - self.assertEqual(mock_wheel.call_count, 1) - self.assertEqual(mock_jar.call_count, 1) + self.assertEqual(mock_extract_jar.call_count, 1) + self.assertEqual(mock_qtlibs.call_count, 1) + self.assertEqual(mock_extraqtmodules.call_count, 1) + self.assertEqual(mock_recipes_exist.call_count, 1) + self.assertEqual(mock_find_jars.call_count, 1) self.assertTrue(self.config_file.exists()) + self.assertTrue(self.buildozer_config.exists()) # test config file contents - config_obj = ConfigFile(config_file=self.config_file) + config_obj = self.deploy_lib.BaseConfig(config_file=self.config_file) self.assertEqual(config_obj.get_value("app", "input_file"), "main.py") self.assertEqual(config_obj.get_value("python", "android_packages"), "buildozer==1.5.0,cython==0.29.33") - self.assertEqual(config_obj.get_value("qt", "wheel_pyside"), + self.assertEqual(config_obj.get_value("android", "wheel_pyside"), str(self.pyside_wheel.resolve())) - self.assertEqual(config_obj.get_value("qt", "wheel_shiboken"), + self.assertEqual(config_obj.get_value("android", "wheel_shiboken"), str(self.shiboken_wheel.resolve())) self.assertEqual(config_obj.get_value("buildozer", "mode"), "debug") self.assertEqual(config_obj.get_value("buildozer", "recipe_dir"), - str(self.temp_example / "deployment" / "recipes")) + '') self.assertEqual(config_obj.get_value("buildozer", "jars_dir"), - str(self.temp_example / "deployment" / "jar" / "PySide6" / "jar")) - self.assertEqual(config_obj.get_value("buildozer", "ndk_path"), "") - self.assertEqual(config_obj.get_value("buildozer", "sdk_path"), "") - self.assertEqual(config_obj.get_value("buildozer", "modules"), "Core,Gui,Widgets") + str(self.temp_example / jar_dir)) + self.assertIn(str(self.ndk_path), config_obj.get_value("buildozer", "ndk_path")) + self.assertEqual(config_obj.get_value("buildozer", "sdk_path"), '') + expected_modules = {"Core", "Gui"} + obtained_modules = set(config_obj.get_value("buildozer", "modules").split(",")) + self.assertEqual(obtained_modules, expected_modules) + expected_local_libs = "plugins_platforms_qtforandroid" self.assertEqual(config_obj.get_value("buildozer", "local_libs"), - "plugins_platforms_qtforandroid") + expected_local_libs) self.assertEqual(config_obj.get_value("buildozer", "arch"), "x86_64") - self.config_file.unlink() - - @patch("android_deploy.extract_and_copy_jar") - @patch("android_deploy.Wheel") - def test_config_with_ndk_sdk(self, mock_jar, mock_wheel): - mock_wheel.version = "6.5.0a1" - self.android_deploy.main(name="android_app", shiboken_wheel=self.shiboken_wheel, - pyside_wheel=self.pyside_wheel, ndk_path=self.ndk_path, - sdk_path=self.sdk_path, init=True, force=True) - - self.assertEqual(mock_wheel.call_count, 1) - self.assertEqual(mock_jar.call_count, 1) - self.assertTrue(self.config_file.exists()) + # test buildozer config file contents + buildozer_config_obj = self.deploy_lib.BaseConfig(config_file=self.buildozer_config) + obtained_jars = set(buildozer_config_obj.get_value("app", "android.add_jars").split(',')) + expected_jars = set(jars) + self.assertEqual(obtained_jars, expected_jars) + obtained_extra_args = buildozer_config_obj.get_value("app", "p4a.extra_args") + extra_args_patrn = re.compile("--qt-libs=(?P.*) --load-local-libs=" + "(?P.*) --init-classes=(?P.*)") + match = extra_args_patrn.search(obtained_extra_args) + obtained_modules = match.group("modules").split(',') + obtained_local_libs = match.group("local_libs") + obtained_init_classes = match.group("init_classes") + self.assertEqual(set(obtained_modules), expected_modules) + self.assertEqual(obtained_local_libs, expected_local_libs) + self.assertEqual(obtained_init_classes, '') + expected_include_exts = "py,png,jpg,kv,atlas,qml,js" + obtained_include_exts = buildozer_config_obj.get_value("app", "source.include_exts") + self.assertEqual(expected_include_exts, obtained_include_exts) - # test config file contents - config_obj = ConfigFile(config_file=self.config_file) - self.assertEqual(config_obj.get_value("buildozer", "ndk_path"), - str(self.ndk_path.resolve())) - self.assertEqual(config_obj.get_value("buildozer", "sdk_path"), - str(self.sdk_path.resolve())) self.config_file.unlink() + self.buildozer_config.unlink() - def test_error_pwd_not_projectdir(self): - os.chdir(self.current_dir) - with self.assertRaises(RuntimeError): + def test_errors(self, mock_extract_jar): + # test if error raises for non existing NDK + with self.assertRaises(FileNotFoundError) as context: self.android_deploy.main(name="android_app", shiboken_wheel=self.shiboken_wheel, - pyside_wheel=self.pyside_wheel, init=True, force=True) + pyside_wheel=self.pyside_wheel, force=True) + self.assertTrue("Unable to find Android NDK" in str(context.exception)) - def test_error_no_wheels(self): + # test when cwd() is not project_dir os.chdir(self.current_dir) - with self.assertRaises(RuntimeError): - self.android_deploy.main(name="android_app", shiboken_wheel=None, + with self.assertRaises(RuntimeError) as context: + self.android_deploy.main(name="android_app", shiboken_wheel=self.shiboken_wheel, pyside_wheel=self.pyside_wheel, init=True, force=True) + self.assertTrue("For Android deployment to work" in str(context.exception)) - @patch("android_deploy.extract_and_copy_jar") - @patch("android_deploy.Wheel") - def test_config_with_Qml(self, mock_jar, mock_wheel): - example_qml_application = self.example_root / "quick" / "models" / "stringlistmodel" - temp_qml_example = Path( - shutil.copytree(example_qml_application, Path(self.temp_dir) / "stringlistmodel") + +@patch("deploy_lib.config.run_qmlimportscanner") +@patch("deploy_lib.android.android_config.extract_and_copy_jar") +class TestPySide6AndroidDeployQml(DeployTestBase): + @classmethod + def setUpClass(cls): + super().setUpClass() + # setting up example + example_qml_application = cls.example_root / "quick" / "models" / "stringlistmodel" + cls.temp_qml_example = Path( + shutil.copytree(example_qml_application, Path(cls.temp_dir) / "stringlistmodel") ).resolve() - config_file = temp_qml_example / "pysidedeploy.spec" - (temp_qml_example / "stringlistmodel.py").rename(temp_qml_example / "main.py") - (temp_qml_example / "stringlistmodel.pyproject").unlink() - os.chdir(temp_qml_example) - mock_wheel.version = "6.5.0a1" + def setUp(self): + os.chdir(self.temp_qml_example) + self.config_file = self.temp_qml_example / "pysidedeploy.spec" + self.buildozer_config_file = self.temp_qml_example / "buildozer.spec" + (self.temp_qml_example / "stringlistmodel.py").rename(self.temp_qml_example / "main.py") + (self.temp_qml_example / "stringlistmodel.pyproject").unlink() + + @patch("deploy_lib.android.buildozer.BuildozerConfig._BuildozerConfig__find_local_libs") + @patch("deploy_lib.android.buildozer.BuildozerConfig._BuildozerConfig__find_jars") + @patch("deploy_lib.android.android_config.AndroidConfig.recipes_exist") + @patch("deploy_lib.android.buildozer.BuildozerConfig." + "_BuildozerConfig__find_dependent_qt_modules") + @patch("deploy_lib.android.buildozer.find_qtlibs_in_wheel") + def test_config_with_Qml(self, mock_qtlibs, mock_extraqtmodules, mock_recipes_exist, + mock_find_jars, mock_local_libs, mock_extract_jar, + mock_qmlimportscanner): + # setting up mocks + jar_dir = "tmp/jar/PySide6/jar" + mock_extract_jar.return_value = Path(jar_dir) + mock_qtlibs.return_value = self.pyside_wheel / "PySide6/Qt/lib" + mock_extraqtmodules.return_value = ['Qml', 'Network', 'QmlModels', 'OpenGL'] + mock_recipes_exist.return_value = True + jars, init_classes = ["/tmp/jar/PySide6/jar/Qt6Android.jar", + "/tmp/jar/PySide6/jar/Qt6AndroidBindings.jar", + "/tmp/jar/PySide6/jar/Qt6AndroidNetworkInformationBackend.jar", + "/tmp/jar/PySide6/jar/Qt6AndroidNetwork.jar"], [] + mock_find_jars.return_value = jars, init_classes + dependent_plugins = ["platforms_qtforandroid", + "platforminputcontexts_qtvirtualkeyboardplugin", + "iconengines_qsvgicon"] + mock_local_libs.return_value = [], dependent_plugins + mock_qmlimportscanner.return_value = ["QtQuick"] self.android_deploy.main(name="android_app", shiboken_wheel=self.shiboken_wheel, - pyside_wheel=self.pyside_wheel, init=True, force=True) - - self.assertEqual(mock_wheel.call_count, 1) - self.assertEqual(mock_jar.call_count, 1) - self.assertTrue(config_file.exists()) + pyside_wheel=self.pyside_wheel, ndk_path=self.ndk_path, + init=True, force=True, keep_deployment_files=True) + + self.assertEqual(mock_extract_jar.call_count, 1) + self.assertEqual(mock_qtlibs.call_count, 1) + self.assertEqual(mock_extraqtmodules.call_count, 1) + self.assertEqual(mock_recipes_exist.call_count, 1) + self.assertEqual(mock_find_jars.call_count, 1) + self.assertEqual(mock_qmlimportscanner.call_count, 1) + self.assertTrue(self.config_file.exists()) + self.assertTrue(self.buildozer_config_file.exists()) # test config file contents - config_obj = ConfigFile(config_file=config_file) - self.assertEqual(config_obj.get_value("buildozer", "modules"), - "Core,Gui,Widgets,Network,OpenGL,Qml,Quick,QuickControls2") - config_file.unlink() - - def tearDown(self) -> None: - super().tearDown() - os.chdir(self.current_dir) + config_obj = self.deploy_lib.BaseConfig(config_file=self.config_file) + expected_modules = {"Quick", "Core", "Gui", "Network", "Qml", "QmlModels", "OpenGL"} + obtained_modules = set(config_obj.get_value("buildozer", "modules").split(",")) + self.assertEqual(obtained_modules, expected_modules) + expected_local_libs = "plugins_platforms_qtforandroid" + self.assertEqual(config_obj.get_value("buildozer", "local_libs"), + expected_local_libs) + expected_qt_plugins = set(dependent_plugins) + obtained_qt_plugins = set(config_obj.get_value("android", "plugins").split(",")) + self.assertEqual(expected_qt_plugins, obtained_qt_plugins) + + # test buildozer config file contents + buildozer_config_obj = self.deploy_lib.BaseConfig(config_file=self.buildozer_config_file) + obtained_jars = set(buildozer_config_obj.get_value("app", "android.add_jars").split(',')) + expected_jars = set(jars) + self.assertEqual(obtained_jars, expected_jars) + obtained_extra_args = buildozer_config_obj.get_value("app", "p4a.extra_args") + extra_args_patrn = re.compile("--qt-libs=(?P.*) --load-local-libs=" + "(?P.*) --init-classes=(?P.*)") + match = extra_args_patrn.search(obtained_extra_args) + obtained_modules = match.group("modules").split(',') + obtained_local_libs = match.group("local_libs") + obtained_init_classes = match.group("init_classes") + self.assertEqual(set(obtained_modules), expected_modules) + self.assertEqual(obtained_local_libs, expected_local_libs) + self.assertEqual(obtained_init_classes, '') - @classmethod - def tearDownClass(cls) -> None: - shutil.rmtree(Path(cls.temp_dir)) + self.config_file.unlink() + self.buildozer_config_file.unlink() if __name__ == "__main__": diff --git a/sources/pyside6/tests/tools/pyside6-deploy/extensive_deploy_test.py b/sources/pyside6/tests/tools/pyside6-deploy/extensive_deploy_test.py index 290cc980..40afc7f5 100644 --- a/sources/pyside6/tests/tools/pyside6-deploy/extensive_deploy_test.py +++ b/sources/pyside6/tests/tools/pyside6-deploy/extensive_deploy_test.py @@ -56,7 +56,7 @@ class TestPySide6Deploy(unittest.TestCase): keep_deployment_files=True, force=True) print("Now testing Widget with config file") - self.deploy.main(self.main_file, config_file=self.config_file, loglevel=logging.INFO, + self.deploy.main(self.main_file, config_file=self.config_file, loglevel=logging.INFO, force=True) def setUpQml(self): diff --git a/sources/pyside6/tests/tools/pyside6-deploy/test_pyside6_deploy.py b/sources/pyside6/tests/tools/pyside6-deploy/test_pyside6_deploy.py index 3a54e2b8..9c743674 100644 --- a/sources/pyside6/tests/tools/pyside6-deploy/test_pyside6_deploy.py +++ b/sources/pyside6/tests/tools/pyside6-deploy/test_pyside6_deploy.py @@ -8,49 +8,53 @@ import sys import os import importlib from pathlib import Path -from configparser import ConfigParser from unittest.mock import patch from unittest import mock -class ConfigFile: - def __init__(self, config_file: Path) -> None: - self.config_file = config_file - self.parser = ConfigParser(comment_prefixes="/", allow_no_value=True) - self.parser.read(self.config_file) +def is_pyenv_python(): + pyenv_root = os.environ.get("PYENV_ROOT") - def get_value(self, section: str, key: str): - return str(self.parser.get(section, key)) + if pyenv_root and (resolved_exe := str(Path(sys.executable).resolve())): + return resolved_exe.startswith(pyenv_root) + return False -class TestPySide6Deploy(unittest.TestCase): +class LongSortedOptionTest(unittest.TestCase): + @staticmethod + def _option_prepare(s): + """ + Take a string and return a list obtained by text.split(). + Options starting with "--" are also sorted." + """ + items = s.split() + for idx in range(len(items)): + if items[idx].startswith("--"): + return items[:idx] + sorted(items[idx:]) + return items + + def assertEqual(self, text_a, text_b): + if (not isinstance(text_a, str) or not isinstance(text_b, str) + or (len(text_a) < 50 and len(text_b) < 50)): + return super().assertEqual(text_a, text_b) + sort_a = self._option_prepare(text_a) + sort_b = self._option_prepare(text_b) + return super().assertEqual(sort_a, sort_b) + + +class DeployTestBase(LongSortedOptionTest): @classmethod def setUpClass(cls): - # PYSIDE-2230: A temporary patch that avoids the pyenv error. - # The final solution is too much for this quick fix. - if os.environ.get("PYENV_ROOT"): - del os.environ["PYENV_ROOT"] cls.pyside_root = Path(__file__).parents[5].resolve() - example_root = cls.pyside_root / "examples" - example_widgets = example_root / "widgets" / "widgets" / "tetrix" - example_qml = example_root / "qml" / "editingmodel" - example_webenginequick = example_root / "webenginequick" / "nanobrowser" + cls.example_root = cls.pyside_root / "examples" cls.temp_dir = tempfile.mkdtemp() - cls.temp_example_widgets = Path( - shutil.copytree(example_widgets, Path(cls.temp_dir) / "tetrix") - ).resolve() - cls.temp_example_qml = Path( - shutil.copytree(example_qml, Path(cls.temp_dir) / "editingmodel") - ).resolve() - cls.temp_example_webenginequick = Path( - shutil.copytree(example_webenginequick, Path(cls.temp_dir) / "nanobrowser") - ).resolve() cls.current_dir = Path.cwd() - cls.linux_onefile_icon = ( - cls.pyside_root / "sources" / "pyside-tools" / "deploy_lib" / "pyside_icon.jpg" - ) - - sys.path.append(str(cls.pyside_root / "sources" / "pyside-tools")) + tools_path = cls.pyside_root / "sources" / "pyside-tools" + cls.win_icon = tools_path / "deploy_lib" / "pyside_icon.ico" + cls.linux_icon = tools_path / "deploy_lib" / "pyside_icon.jpg" + cls.macos_icon = tools_path / "deploy_lib" / "pyside_icon.icns" + if tools_path not in sys.path: + sys.path.append(str(cls.pyside_root / "sources" / "pyside-tools")) cls.deploy_lib = importlib.import_module("deploy_lib") cls.deploy = importlib.import_module("deploy") sys.modules["deploy"] = cls.deploy @@ -61,31 +65,54 @@ class TestPySide6Deploy(unittest.TestCase): # print no outputs to stdout sys.stdout = mock.MagicMock() - def setUpWidgets(self): + @classmethod + def tearDownClass(cls) -> None: + shutil.rmtree(Path(cls.temp_dir)) + + def tearDown(self) -> None: + super().tearDown() + os.chdir(self.current_dir) + + +class TestPySide6DeployWidgets(DeployTestBase): + @classmethod + def setUpClass(cls): + super().setUpClass() + example_widgets = cls.example_root / "widgets" / "widgets" / "tetrix" + cls.temp_example_widgets = Path( + shutil.copytree(example_widgets, Path(cls.temp_dir) / "tetrix") + ).resolve() + + def setUp(self): os.chdir(self.temp_example_widgets) self.main_file = self.temp_example_widgets / "tetrix.py" self.deployment_files = self.temp_example_widgets / "deployment" self.expected_run_cmd = ( f"{sys.executable} -m nuitka {str(self.main_file)} --follow-imports --onefile" f" --enable-plugin=pyside6 --output-dir={str(self.deployment_files)} --quiet" - f" --noinclude-qt-translations=True" + f" --noinclude-qt-translations" ) if sys.platform.startswith("linux"): - self.expected_run_cmd += f" --linux-onefile-icon={str(self.linux_onefile_icon)}" + self.expected_run_cmd += f" --linux-icon={str(self.linux_icon)}" + elif sys.platform == "darwin": + self.expected_run_cmd += f" --macos-app-icon={str(self.macos_icon)}" + elif sys.platform == "win32": + self.expected_run_cmd += f" --windows-icon-from-ico={str(self.win_icon)}" + + if is_pyenv_python(): + self.expected_run_cmd += " --static-libpython=no" self.config_file = self.temp_example_widgets / "pysidedeploy.spec" def testWidgetDryRun(self): # Checking for dry run commands is equivalent to mocking the # subprocess.check_call() in commands.py as the the dry run command # is the command being run. - self.setUpWidgets() original_output = self.deploy.main(self.main_file, dry_run=True, force=True) self.assertEqual(original_output, self.expected_run_cmd) def testWidgetConfigFile(self): # includes both dry run and config_file tests - self.setUpWidgets() # init init_result = self.deploy.main(self.main_file, init=True, force=True) self.assertEqual(init_result, None) @@ -95,19 +122,37 @@ class TestPySide6Deploy(unittest.TestCase): self.assertEqual(original_output, self.expected_run_cmd) # # test config file contents - config_obj = ConfigFile(config_file=self.config_file) + config_obj = self.deploy_lib.BaseConfig(config_file=self.config_file) self.assertEqual(config_obj.get_value("app", "input_file"), "tetrix.py") self.assertEqual(config_obj.get_value("app", "project_dir"), ".") self.assertEqual(config_obj.get_value("app", "exec_directory"), ".") - self.assertEqual(config_obj.get_value("python", "packages"), "nuitka==1.5.4,ordered_set,zstandard") + self.assertEqual(config_obj.get_value("python", "packages"), + "nuitka==1.8.0,ordered_set,zstandard") self.assertEqual(config_obj.get_value("qt", "qml_files"), "") - self.assertEqual( - config_obj.get_value("nuitka", "extra_args"), "--quiet --noinclude-qt-translations=True" - ) + equ_base = "--quiet --noinclude-qt-translations" + equ_value = equ_base + " --static-libpython=no" if is_pyenv_python() else equ_base + self.assertEqual(config_obj.get_value("nuitka", "extra_args"), equ_value) self.assertEqual(config_obj.get_value("qt", "excluded_qml_plugins"), "") self.config_file.unlink() - def setUpQml(self): + def testErrorReturns(self): + # main file and config file does not exists + fake_main_file = self.main_file.parent / "main.py" + with self.assertRaises(RuntimeError) as context: + self.deploy.main(main_file=fake_main_file, config_file=self.config_file) + self.assertTrue("Directory does not contain main.py file." in str(context.exception)) + + +class TestPySide6DeployQml(DeployTestBase): + @classmethod + def setUpClass(cls): + super().setUpClass() + example_qml = cls.example_root / "qml" / "editingmodel" + cls.temp_example_qml = Path( + shutil.copytree(example_qml, Path(cls.temp_dir) / "editingmodel") + ).resolve() + + def setUp(self): os.chdir(self.temp_example_qml) self.main_file = self.temp_example_qml / "main.py" self.deployment_files = self.temp_example_qml / "deployment" @@ -116,32 +161,37 @@ class TestPySide6Deploy(unittest.TestCase): self.expected_run_cmd = ( f"{sys.executable} -m nuitka {str(self.main_file)} --follow-imports --onefile" f" --enable-plugin=pyside6 --output-dir={str(self.deployment_files)} --quiet" - f" --noinclude-qt-translations=True --include-qt-plugins=all" + f" --noinclude-qt-translations --include-qt-plugins=all" f" --include-data-files={str(self.temp_example_qml / self.first_qml_file)}=" f"./main.qml --include-data-files=" f"{str(self.temp_example_qml /self.second_qml_file)}=./MovingRectangle.qml" - ) + ) if sys.platform != "win32": self.expected_run_cmd += ( " --noinclude-dlls=libQt6Charts*" " --noinclude-dlls=libQt6Quick3D* --noinclude-dlls=libQt6Sensors*" " --noinclude-dlls=libQt6Test* --noinclude-dlls=libQt6WebEngine*" - ) + ) else: self.expected_run_cmd += ( " --noinclude-dlls=Qt6Charts*" " --noinclude-dlls=Qt6Quick3D* --noinclude-dlls=Qt6Sensors*" " --noinclude-dlls=Qt6Test* --noinclude-dlls=Qt6WebEngine*" - ) + ) if sys.platform.startswith("linux"): - self.expected_run_cmd += f" --linux-onefile-icon={str(self.linux_onefile_icon)}" + self.expected_run_cmd += f" --linux-icon={str(self.linux_icon)}" + elif sys.platform == "darwin": + self.expected_run_cmd += f" --macos-app-icon={str(self.macos_icon)}" + elif sys.platform == "win32": + self.expected_run_cmd += f" --windows-icon-from-ico={str(self.win_icon)}" + + if is_pyenv_python(): + self.expected_run_cmd += " --static-libpython=no" self.config_file = self.temp_example_qml / "pysidedeploy.spec" def testQmlConfigFile(self): - self.setUpQml() - # create config file with patch("deploy_lib.config.run_qmlimportscanner") as mock_qmlimportscanner: mock_qmlimportscanner.return_value = ["QtQuick"] @@ -149,15 +199,16 @@ class TestPySide6Deploy(unittest.TestCase): self.assertEqual(init_result, None) # test config file contents - config_obj = ConfigFile(config_file=self.config_file) + config_obj = self.deploy_lib.BaseConfig(config_file=self.config_file) self.assertEqual(config_obj.get_value("app", "input_file"), "main.py") self.assertEqual(config_obj.get_value("app", "project_dir"), ".") self.assertEqual(config_obj.get_value("app", "exec_directory"), ".") - self.assertEqual(config_obj.get_value("python", "packages"), "nuitka==1.5.4,ordered_set,zstandard") + self.assertEqual(config_obj.get_value("python", "packages"), + "nuitka==1.8.0,ordered_set,zstandard") self.assertEqual(config_obj.get_value("qt", "qml_files"), "main.qml,MovingRectangle.qml") - self.assertEqual( - config_obj.get_value("nuitka", "extra_args"), "--quiet --noinclude-qt-translations=True" - ) + equ_base = "--quiet --noinclude-qt-translations" + equ_value = equ_base + " --static-libpython=no" if is_pyenv_python() else equ_base + self.assertEqual(config_obj.get_value("nuitka", "extra_args"), equ_value) self.assertEqual( config_obj.get_value("qt", "excluded_qml_plugins"), "QtCharts,QtQuick3D,QtSensors,QtTest,QtWebEngine", @@ -165,7 +216,6 @@ class TestPySide6Deploy(unittest.TestCase): self.config_file.unlink() def testQmlDryRun(self): - self.setUpQml() with patch("deploy_lib.config.run_qmlimportscanner") as mock_qmlimportscanner: mock_qmlimportscanner.return_value = ["QtQuick"] original_output = self.deploy.main(self.main_file, dry_run=True, force=True) @@ -173,13 +223,22 @@ class TestPySide6Deploy(unittest.TestCase): self.assertEqual(mock_qmlimportscanner.call_count, 1) def testMainFileDryRun(self): - self.setUpQml() with patch("deploy_lib.config.run_qmlimportscanner") as mock_qmlimportscanner: mock_qmlimportscanner.return_value = ["QtQuick"] original_output = self.deploy.main(Path.cwd() / "main.py", dry_run=True, force=True) self.assertEqual(original_output, self.expected_run_cmd) self.assertEqual(mock_qmlimportscanner.call_count, 1) + +class TestPySide6DeployWebEngine(DeployTestBase): + @classmethod + def setUpClass(cls): + super().setUpClass() + example_webenginequick = cls.example_root / "webenginequick" / "nanobrowser" + cls.temp_example_webenginequick = Path( + shutil.copytree(example_webenginequick, Path(cls.temp_dir) / "nanobrowser") + ).resolve() + # this test case retains the QtWebEngine dlls def testWebEngineQuickDryRun(self): # setup @@ -203,7 +262,7 @@ class TestPySide6Deploy(unittest.TestCase): expected_run_cmd = ( f"{sys.executable} -m nuitka {str(main_file)} --follow-imports --onefile" f" --enable-plugin=pyside6 --output-dir={str(deployment_files)} --quiet" - f" --noinclude-qt-translations=True --include-qt-plugins=all" + f" --noinclude-qt-translations --include-qt-plugins=all" f" {data_files_cmd}" ) @@ -212,16 +271,20 @@ class TestPySide6Deploy(unittest.TestCase): " --noinclude-dlls=libQt6Charts*" " --noinclude-dlls=libQt6Quick3D* --noinclude-dlls=libQt6Sensors*" " --noinclude-dlls=libQt6Test*" - ) + ) else: expected_run_cmd += ( " --noinclude-dlls=Qt6Charts*" " --noinclude-dlls=Qt6Quick3D* --noinclude-dlls=Qt6Sensors*" " --noinclude-dlls=Qt6Test*" - ) + ) if sys.platform.startswith("linux"): - expected_run_cmd += f" --linux-onefile-icon={str(self.linux_onefile_icon)}" + expected_run_cmd += f" --linux-icon={str(self.linux_icon)}" + elif sys.platform == "darwin": + expected_run_cmd += f" --macos-app-icon={str(self.macos_icon)}" + elif sys.platform == "win32": + expected_run_cmd += f" --windows-icon-from-ico={str(self.win_icon)}" config_file = self.temp_example_webenginequick / "pysidedeploy.spec" @@ -237,7 +300,7 @@ class TestPySide6Deploy(unittest.TestCase): self.assertEqual(mock_qmlimportscanner.call_count, 2) # test config file contents - config_obj = ConfigFile(config_file=config_file) + config_obj = self.deploy_lib.BaseConfig(config_file=config_file) self.assertEqual(config_obj.get_value("app", "input_file"), "quicknanobrowser.py") self.assertEqual(config_obj.get_value("qt", "qml_files"), ",".join(qml_files)) self.assertEqual( @@ -245,14 +308,6 @@ class TestPySide6Deploy(unittest.TestCase): "QtCharts,QtQuick3D,QtSensors,QtTest", ) - def tearDown(self) -> None: - super().tearDown() - os.chdir(self.current_dir) - - @classmethod - def tearDownClass(cls) -> None: - shutil.rmtree(Path(cls.temp_dir)) - if __name__ == "__main__": unittest.main() diff --git a/sources/pyside6/tests/util/helper/docmodifier.py b/sources/pyside6/tests/util/helper/docmodifier.py index 5a242160..cfb66564 100644 --- a/sources/pyside6/tests/util/helper/docmodifier.py +++ b/sources/pyside6/tests/util/helper/docmodifier.py @@ -83,9 +83,9 @@ if __name__ == '__main__': doc_filter = lambda x: x.startswith('test') doc_suffix = 'suffix' - assert(Implementing.testBase.__doc__ == 'prefixbasesuffix') - assert(Implementing.testWithoutDoc.__doc__ == None) - assert(OnlySuffix.testBase.__doc__ == 'basesuffix') - assert(OnlySuffix.testWithoutDoc.__doc__ == None) - assert(OnlyPrefix.testBase.__doc__ == 'prefixbase') - assert(OnlyPrefix.testWithoutDoc.__doc__ == None) + assert (Implementing.testBase.__doc__ == 'prefixbasesuffix') + assert (Implementing.testWithoutDoc.__doc__ == None) + assert (OnlySuffix.testBase.__doc__ == 'basesuffix') + assert (OnlySuffix.testWithoutDoc.__doc__ == None) + assert (OnlyPrefix.testBase.__doc__ == 'prefixbase') + assert (OnlyPrefix.testWithoutDoc.__doc__ == None) diff --git a/sources/pyside6/tests/util/helper/usesqapplication.py b/sources/pyside6/tests/util/helper/usesqapplication.py index d6d070c6..f62e320f 100644 --- a/sources/pyside6/tests/util/helper/usesqapplication.py +++ b/sources/pyside6/tests/util/helper/usesqapplication.py @@ -12,6 +12,7 @@ import unittest # This should work with every compatible library. # Replaces the QtGui and QtCore versions as well. + class UsesQApplication(unittest.TestCase): '''Helper class to provide Q(Core|Gui|)Application instances Just connect or call self.exit_app_cb. When called, will ask diff --git a/sources/pyside6/tests/util/processtimer.py b/sources/pyside6/tests/util/processtimer.py index fb38943c..e471cf16 100644 --- a/sources/pyside6/tests/util/processtimer.py +++ b/sources/pyside6/tests/util/processtimer.py @@ -27,7 +27,7 @@ class ProcessTimer(object): def waitfor(self): time_passed = 0 - while(self.proc.poll() is None and time_passed < self.timeout): + while (self.proc.poll() is None and time_passed < self.timeout): time_passed = time_passed + 1 time.sleep(1) diff --git a/sources/shiboken6/.cmake.conf b/sources/shiboken6/.cmake.conf index 7a005078..836ed305 100644 --- a/sources/shiboken6/.cmake.conf +++ b/sources/shiboken6/.cmake.conf @@ -1,5 +1,5 @@ set(shiboken_MAJOR_VERSION "6") set(shiboken_MINOR_VERSION "6") -set(shiboken_MICRO_VERSION "1") +set(shiboken_MICRO_VERSION "2") set(shiboken_PRE_RELEASE_VERSION_TYPE "") set(shiboken_PRE_RELEASE_VERSION "") diff --git a/sources/shiboken6/ApiExtractor/abstractmetabuilder.cpp b/sources/shiboken6/ApiExtractor/abstractmetabuilder.cpp index 53be95ca..e922f8d5 100644 --- a/sources/shiboken6/ApiExtractor/abstractmetabuilder.cpp +++ b/sources/shiboken6/ApiExtractor/abstractmetabuilder.cpp @@ -2162,7 +2162,8 @@ AbstractMetaFunction *AbstractMetaBuilderPrivate::traverseFunction(const Functio if (!currentClass || currentClass->typeEntry()->generateCode()) { const QString signature = qualifiedFunctionSignatureWithType(functionItem, className); qCWarning(lcShiboken, "%s", - qPrintable(msgStrippingArgument(functionItem, i, signature, arg))); + qPrintable(msgStrippingArgument(functionItem, i, signature, + arg, errorMessage))); } break; } diff --git a/sources/shiboken6/ApiExtractor/abstractmetafunction.cpp b/sources/shiboken6/ApiExtractor/abstractmetafunction.cpp index 23a44248..6bcf5ea9 100644 --- a/sources/shiboken6/ApiExtractor/abstractmetafunction.cpp +++ b/sources/shiboken6/ApiExtractor/abstractmetafunction.cpp @@ -975,12 +975,14 @@ QString AbstractMetaFunctionPrivate::formatMinimalSignature(const AbstractMetaFu { QString result = m_originalName + u'('; for (qsizetype i = 0; i < m_arguments.size(); ++i) { + const auto &argument = m_arguments.at(i); if (i > 0) result += u','; - result += comment - ? m_arguments.at(i).modifiedType().minimalSignature() - : m_arguments.at(i).type().minimalSignature(); + const auto &type = comment ? argument.modifiedType() : argument.type(); + result += type.minimalSignature(); + if (comment && argument.hasDefaultValueExpression()) + result += u'='; } result += u')'; if (m_constant) diff --git a/sources/shiboken6/ApiExtractor/abstractmetalang.cpp b/sources/shiboken6/ApiExtractor/abstractmetalang.cpp index ce072537..87e215c8 100644 --- a/sources/shiboken6/ApiExtractor/abstractmetalang.cpp +++ b/sources/shiboken6/ApiExtractor/abstractmetalang.cpp @@ -1446,6 +1446,11 @@ void AbstractMetaClassPrivate::addUsingConstructors(const AbstractMetaClassPtr & } } +static inline bool isSignal(const AbstractMetaFunctionCPtr &f) +{ + return f->isSignal(); +} + void AbstractMetaClass::fixFunctions(const AbstractMetaClassPtr &klass) { auto *d = klass->d.data(); @@ -1482,6 +1487,10 @@ void AbstractMetaClass::fixFunctions(const AbstractMetaClassPtr &klass) *superClass -= AbstractMetaClass::FinalInTargetLang; } superFuncs = superClass->queryFunctions(FunctionQueryOption::ClassImplements); + // We are not interested in signals as no bindings are generated for them; + // they cause documentation warnings. + superFuncs.erase(std::remove_if(superFuncs.begin(), superFuncs.end(), isSignal), + superFuncs.end()); const auto virtuals = superClass->queryFunctions(FunctionQueryOption::VirtualInCppFunctions); superFuncs += virtuals; diff --git a/sources/shiboken6/ApiExtractor/abstractmetatype.cpp b/sources/shiboken6/ApiExtractor/abstractmetatype.cpp index e913e10f..d9f5bde4 100644 --- a/sources/shiboken6/ApiExtractor/abstractmetatype.cpp +++ b/sources/shiboken6/ApiExtractor/abstractmetatype.cpp @@ -7,6 +7,8 @@ #include "messages.h" #include "typedatabase.h" #include "containertypeentry.h" +#include "enumtypeentry.h" +#include "flagstypeentry.h" #include "qtcompat.h" #include "typeinfo.h" @@ -698,10 +700,12 @@ QString AbstractMetaTypeData::formatPythonSignature() const if (m_typeEntry->isPrimitive()) for (Indirection i : m_indirections) result += TypeInfo::indirectionKeyword(i); - // If it is a flags type, we replace it with the full name: - // "PySide6.QtCore.Qt.ItemFlags" instead of "PySide6.QtCore.QFlags" - if (m_typeEntry->isFlags()) - result = m_typeEntry->qualifiedTargetLangName(); + // If it is a flags type, we replace it with the full name of the enum: + // "PySide6.QtCore.Qt.ItemFlag" instead of "PySide6.QtCore.QFlags" + if (m_typeEntry->isFlags()) { + const auto fte = std::static_pointer_cast(m_typeEntry); + result = fte->originator()->qualifiedTargetLangName(); + } result.replace(u"::"_s, u"."_s); return result; } diff --git a/sources/shiboken6/ApiExtractor/abstractmetatype.h b/sources/shiboken6/ApiExtractor/abstractmetatype.h index b37f7309..dd3d96bf 100644 --- a/sources/shiboken6/ApiExtractor/abstractmetatype.h +++ b/sources/shiboken6/ApiExtractor/abstractmetatype.h @@ -10,6 +10,7 @@ #include "typedatabase_typedefs.h" #include +#include #include #include #include @@ -253,6 +254,9 @@ public: #endif private: + friend size_t qHash(const AbstractMetaType &t, size_t seed = 0) noexcept + { return qHash(t.typeEntry().get(), seed); } + friend class AbstractMetaTypeData; QSharedDataPointer d; @@ -266,9 +270,6 @@ inline bool operator==(const AbstractMetaType &t1, const AbstractMetaType &t2) inline bool operator!=(const AbstractMetaType &t1, const AbstractMetaType &t2) { return !t1.equals(t2); } -inline size_t qHash(const AbstractMetaType &t, size_t seed) -{ return qHash(t.typeEntry().get(), seed); } - #ifndef QT_NO_DEBUG_STREAM QDebug operator<<(QDebug d, const AbstractMetaType &at); QDebug operator<<(QDebug d, const AbstractMetaType *at); diff --git a/sources/shiboken6/ApiExtractor/clangparser/clangutils.cpp b/sources/shiboken6/ApiExtractor/clangparser/clangutils.cpp index 68b10d5b..4e1f1ab6 100644 --- a/sources/shiboken6/ApiExtractor/clangparser/clangutils.cpp +++ b/sources/shiboken6/ApiExtractor/clangparser/clangutils.cpp @@ -10,29 +10,27 @@ #include -bool operator==(const CXCursor &c1, const CXCursor &c2) +bool operator==(const CXCursor &c1, const CXCursor &c2) noexcept { return c1.kind == c2.kind && c1.xdata == c2.xdata && std::equal(c1.data, c1.data + sizeof(c1.data) / sizeof(c1.data[0]), c2.data); } -size_t qHash(const CXCursor &c, size_t seed) +size_t qHash(const CXCursor &c, size_t seed) noexcept { - return qHash(c.kind) ^ qHash(c.xdata) ^ qHash(c.data[0]) - ^ qHash(c.data[1]) ^ qHash(c.data[2]) ^ seed; + return qHashMulti(seed, c.kind, c.xdata, c.data[0], c.data[1], c.data[2]); } -bool operator==(const CXType &t1, const CXType &t2) +bool operator==(const CXType &t1, const CXType &t2) noexcept { return t1.kind == t2.kind && t1.data[0] == t2.data[0] && t1.data[1] == t2.data[1]; } -size_t qHash(const CXType &ct, size_t seed) +size_t qHash(const CXType &ct, size_t seed) noexcept { - return size_t(ct.kind) ^ size_t(0xFFFFFFFF & quintptr(ct.data[0])) - ^ size_t(0xFFFFFFFF & quintptr(ct.data[1])) ^ seed; + return qHashMulti(seed, ct.kind, ct.data[0], ct.data[1]); } namespace clang { diff --git a/sources/shiboken6/ApiExtractor/clangparser/clangutils.h b/sources/shiboken6/ApiExtractor/clangparser/clangutils.h index 4ad21eeb..eaa04dae 100644 --- a/sources/shiboken6/ApiExtractor/clangparser/clangutils.h +++ b/sources/shiboken6/ApiExtractor/clangparser/clangutils.h @@ -14,11 +14,11 @@ QT_FORWARD_DECLARE_CLASS(QDebug) -bool operator==(const CXCursor &c1, const CXCursor &c2); -size_t qHash(const CXCursor &c, size_t seed = 0); +bool operator==(const CXCursor &c1, const CXCursor &c2) noexcept; +size_t qHash(const CXCursor &c, size_t seed = 0) noexcept; -bool operator==(const CXType &t1, const CXType &t2); -size_t qHash(const CXType &ct, size_t seed); +bool operator==(const CXType &t1, const CXType &t2) noexcept; +size_t qHash(const CXType &ct, size_t seed = 0) noexcept; namespace clang { diff --git a/sources/shiboken6/ApiExtractor/doxygenparser.cpp b/sources/shiboken6/ApiExtractor/doxygenparser.cpp index 2fdc3d1f..bcfbc39d 100644 --- a/sources/shiboken6/ApiExtractor/doxygenparser.cpp +++ b/sources/shiboken6/ApiExtractor/doxygenparser.cpp @@ -119,7 +119,7 @@ void DoxygenParser::fillDocumentation(const AbstractMetaClassPtr &metaClass) + func->originalName() + u"\"]"_s; if (func->arguments().isEmpty()) { - QString args = func->isConstant() ? u"() const "_s : u"()"_s; + QString args = func->isConstant() ? u"() const"_s : u"()"_s; query += u"/../argsstring[text()=\""_s + args + u"\"]"_s; } else { int i = 1; diff --git a/sources/shiboken6/ApiExtractor/include.cpp b/sources/shiboken6/ApiExtractor/include.cpp index d2d57781..38f6ce74 100644 --- a/sources/shiboken6/ApiExtractor/include.cpp +++ b/sources/shiboken6/ApiExtractor/include.cpp @@ -33,11 +33,6 @@ int Include::compare(const Include &rhs) const return m_name.compare(rhs.m_name); } -size_t qHash(const Include& inc) -{ - return qHash(inc.m_name); -} - QTextStream& operator<<(QTextStream& out, const Include& g) { if (g.isValid()) diff --git a/sources/shiboken6/ApiExtractor/include.h b/sources/shiboken6/ApiExtractor/include.h index 7fd7d3b3..dcf8976c 100644 --- a/sources/shiboken6/ApiExtractor/include.h +++ b/sources/shiboken6/ApiExtractor/include.h @@ -4,6 +4,7 @@ #ifndef INCLUDE_H #define INCLUDE_H +#include #include #include @@ -42,15 +43,17 @@ public: QString toString() const; - friend size_t qHash(const Include &); int compare(const Include &rhs) const; - private: - IncludeType m_type = IncludePath; - QString m_name; -}; +private: + friend size_t qHash(Include &inc, size_t seed = 0) noexcept + { + return qHashMulti(seed, inc.m_type, inc.m_name); + } -size_t qHash(const Include& inc); + IncludeType m_type = IncludePath; + QString m_name; +}; inline bool operator<(const Include &lhs, const Include &rhs) { diff --git a/sources/shiboken6/ApiExtractor/messages.cpp b/sources/shiboken6/ApiExtractor/messages.cpp index e6393451..766b05be 100644 --- a/sources/shiboken6/ApiExtractor/messages.cpp +++ b/sources/shiboken6/ApiExtractor/messages.cpp @@ -377,14 +377,15 @@ QString msgGlobalFunctionNotDefined(const FunctionTypeEntryCPtr &fte, QString msgStrippingArgument(const FunctionModelItem &f, int i, const QString &originalSignature, - const ArgumentModelItem &arg) + const ArgumentModelItem &arg, + const QString &reason) { QString result; QTextStream str(&result); str << f->sourceLocation() << "Stripping argument #" << (i + 1) << " of " << originalSignature << " due to unmatched type \"" << arg->type().toString() << "\" with default expression \"" - << arg->defaultValueExpression() << "\"."; + << arg->defaultValueExpression() << "\": " << reason; return result; } @@ -861,6 +862,12 @@ QString msgCannotFindSnippet(const QString &file, const QString &snippetLabel) return result; } +QString msgSnippetError(const QString &context, const char *what) +{ + return "Error processing code snippet of "_L1 + context + + ": "_L1 + QString::fromUtf8(what); +} + QString msgUnableToResolveTypedef(const QString &sourceType, const QString &sourceName) { QString result; diff --git a/sources/shiboken6/ApiExtractor/messages.h b/sources/shiboken6/ApiExtractor/messages.h index d1198b8b..2899cbdf 100644 --- a/sources/shiboken6/ApiExtractor/messages.h +++ b/sources/shiboken6/ApiExtractor/messages.h @@ -99,7 +99,8 @@ QString msgGlobalFunctionNotDefined(const FunctionTypeEntryCPtr &fte, QString msgStrippingArgument(const FunctionModelItem &f, int i, const QString &originalSignature, - const ArgumentModelItem &arg); + const ArgumentModelItem &arg, + const QString &reason); QString msgEnumNotDefined(const EnumTypeEntryCPtr &t); @@ -208,6 +209,7 @@ QString msgIncorrectlyNestedName(const QString &name); QString msgCannotFindView(const QString &viewedName, const QString &name); QString msgCannotFindSnippet(const QString &file, const QString &snippetLabel); +QString msgSnippetError(const QString &context, const char *what); QString msgUnableToResolveTypedef(const QString &sourceType, const QString &sourceName); QString msgCyclicDependency(const QString &funcName, const QString &graphName, diff --git a/sources/shiboken6/cmake/ShibokenHelpers.cmake b/sources/shiboken6/cmake/ShibokenHelpers.cmake index b71f01eb..1a46e5b2 100644 --- a/sources/shiboken6/cmake/ShibokenHelpers.cmake +++ b/sources/shiboken6/cmake/ShibokenHelpers.cmake @@ -15,7 +15,7 @@ endmacro() macro(set_debug_build) set(SHIBOKEN_BUILD_TYPE "Debug") - if(NOT PYTHON_DEBUG_LIBRARIES) + if(NOT Python_LIBRARIES) message(WARNING "Python debug shared library not found; \ assuming python was built with shared library support disabled.") endif() @@ -116,7 +116,7 @@ macro(shiboken_internal_set_python_site_packages) endif() else() execute_process( - COMMAND ${PYTHON_EXECUTABLE} -c "if True: + COMMAND ${Python_EXECUTABLE} -c "if True: import sysconfig from os.path import sep @@ -202,6 +202,12 @@ macro(get_python_extension_suffix) # Python_SOABI is only set by CMake 3.17+ # TODO: Lower this to CMake 3.16 if possible. if(SHIBOKEN_IS_CROSS_BUILD) + # For android platform armv7a FindPython module return Python_SOABI as empty because + # it is unable to set Python_CONFIG i.e. find `python3-config` script + # This workaround sets the Python_SOABI manually for this platform. + if(CMAKE_SYSTEM_NAME STREQUAL "Android" AND CMAKE_SYSTEM_PROCESSOR STREQUAL "armv7-a") + set(Python_SOABI "cpython-310}") + endif() if(NOT Python_SOABI) message(FATAL_ERROR "Python_SOABI variable is empty.") endif() @@ -209,7 +215,7 @@ macro(get_python_extension_suffix) else() # See PYSIDE-1841 / https://bugs.python.org/issue39825 for distutils vs sysconfig execute_process( - COMMAND ${PYTHON_EXECUTABLE} -c "if True: + COMMAND ${Python_EXECUTABLE} -c "if True: import sys if sys.version_info >= (3, 8, 2): import sysconfig @@ -241,18 +247,22 @@ macro(shiboken_check_if_limited_api) # TODO: Figure out how to use limited API libs when cross-building to Windows, if that's ever # needed. Perhaps use host python to walk the libs of the target python installation. - if(NOT SHIBOKEN_IS_CROSS_BUILD) + if(NOT SHIBOKEN_IS_CROSS_BUILD AND WIN32) # On Windows, PYTHON_LIBRARIES can be a list. Example: # optimized;C:/Python36/libs/python36.lib;debug;C:/Python36/libs/python36_d.lib # On other platforms, this result is not used at all. execute_process( - COMMAND ${PYTHON_EXECUTABLE} -c "if True: - import os - for lib in '${PYTHON_LIBRARIES}'.split(';'): - if '/' in lib and os.path.isfile(lib): - prefix, py = lib.rsplit('/', 1) + COMMAND ${Python_EXECUTABLE} -c "if True: + from pathlib import Path + libs = r'${Python_LIBRARIES}' + libs = libs.split(';') + for lib in libs: + if '\\\\' in lib and Path(lib).is_file(): + lib = Path(lib) + prefix = lib.parent + py = lib.name if py.startswith('python3'): - print(prefix + '/python3.lib') + print(prefix / 'python3.lib') break " OUTPUT_VARIABLE PYTHON_LIMITED_LIBRARIES @@ -271,6 +281,10 @@ endmacro() macro(shiboken_find_required_python) + set(_shiboken_find_python_version_args "") + if(${ARGC} GREATER 0) + list(APPEND _shiboken_find_python_version_args "${ARGV0}") + endif() # This function can also be called by consumers of ShibokenConfig.cmake package like pyside, # that's why we also check for PYSIDE_IS_CROSS_BUILD (which is set by pyside project) # and QFP_FIND_NEW_PYTHON_PACKAGE for an explicit opt in. @@ -278,11 +292,6 @@ macro(shiboken_find_required_python) # We have to use FindPython package instead of FindPythonInterp to get required target Python # information. if(SHIBOKEN_IS_CROSS_BUILD OR PYSIDE_IS_CROSS_BUILD OR QFP_FIND_NEW_PYTHON_PACKAGE) - set(_shiboken_find_python_version_args "") - if(${ARGC} GREATER 0) - list(APPEND _shiboken_find_python_version_args "${ARGV0}") - endif() - # We want FindPython to look in the sysroot for the python-config executable, # but toolchain files might set CMAKE_FIND_ROOT_PATH_MODE_PROGRAM to NEVER because # programs are mostly found for running and you usually can't run a target executable on @@ -311,40 +320,23 @@ macro(shiboken_find_required_python) "${_shiboken_backup_CMAKE_FIND_ROOT_PATH_MODE_PROGRAM}") set(CMAKE_FIND_ROOT_PATH "${_shiboken_backup_CMAKE_FIND_ROOT_PATH}") - - # Mirror the variables that FindPythonInterp sets, instead of conditionally checking - # and modifying all the places where the variables are used. - set(PYTHON_EXECUTABLE "${Python_EXECUTABLE}") - set(PYTHON_VERSION "${Python_VERSION}") - set(PYTHON_LIBRARIES "${Python_LIBRARIES}") - set(PYTHON_INCLUDE_DIRS "${Python_INCLUDE_DIRS}") - set(PYTHONINTERP_FOUND "${Python_Interpreter_FOUND}") - set(PYTHONINTERP_FOUND "${Python_Interpreter_FOUND}") - set(PYTHONLIBS_FOUND "${Python_Development_FOUND}") - set(PYTHON_VERSION_MAJOR "${Python_VERSION_MAJOR}") - set(PYTHON_VERSION_MINOR "${Python_VERSION_MINOR}") - set(PYTHON_VERSION_PATCH "${Python_VERSION_PATCH}") else() - if(${ARGC} GREATER 0) - find_package(PythonInterp ${ARGV0} REQUIRED) - find_package(PythonLibs ${ARGV0} REQUIRED) - else() - # If no version is specified, just use any interpreter that can be found (from PATH). - # This is useful for super-project builds, so that the default system interpeter - # gets picked up (e.g. /usr/bin/python and not /usr/bin/python2.7). - find_package(PythonInterp REQUIRED) - find_package(PythonLibs REQUIRED) - endif() + find_package( + Python + ${_shiboken_find_python_version_args} + REQUIRED + COMPONENTS Interpreter Development + ) endif() shiboken_validate_python_version() - set(SHIBOKEN_PYTHON_INTERPRETER "${PYTHON_EXECUTABLE}") - set_property(GLOBAL PROPERTY SHIBOKEN_PYTHON_INTERPRETER "${PYTHON_EXECUTABLE}") + set(SHIBOKEN_PYTHON_INTERPRETER "${Python_EXECUTABLE}") + set_property(GLOBAL PROPERTY SHIBOKEN_PYTHON_INTERPRETER "${Python_EXECUTABLE}") endmacro() macro(shiboken_validate_python_version) - if(PYTHON_VERSION_MAJOR EQUAL "3" AND PYTHON_VERSION_MINOR LESS "7") + if(Python_VERSION_MAJOR EQUAL "3" AND Python_VERSION_MINOR LESS "7") message(FATAL_ERROR "Shiboken requires Python 3.7+.") endif() @@ -365,14 +357,14 @@ macro(shiboken_compute_python_includes) if (SHIBOKEN_COMPUTE_INCLUDES_IS_CALLED_FROM_EXPORT) #TODO target_include_directories works on imported targets only starting with v3.11.0. set_property(TARGET Shiboken6::libshiboken - APPEND PROPERTY INTERFACE_INCLUDE_DIRECTORIES ${PYTHON_INCLUDE_DIRS}) + APPEND PROPERTY INTERFACE_INCLUDE_DIRECTORIES ${Python_INCLUDE_DIRS}) else() target_include_directories(libshiboken - PUBLIC $) + PUBLIC $) endif() - set(SHIBOKEN_PYTHON_INCLUDE_DIRS "${PYTHON_INCLUDE_DIRS}") + set(SHIBOKEN_PYTHON_INCLUDE_DIRS "${Python_INCLUDE_DIRS}") set_property(GLOBAL PROPERTY shiboken_python_include_dirs "${SHIBOKEN_PYTHON_INCLUDE_DIRS}") @@ -432,16 +424,8 @@ macro(shiboken_compute_python_libraries) set(SHIBOKEN_PYTHON_LIBRARIES "") endif() - if(CMAKE_BUILD_TYPE STREQUAL "Debug") - if(WIN32 AND NOT SHIBOKEN_PYTHON_LIBRARIES) - set(SHIBOKEN_PYTHON_LIBRARIES ${PYTHON_DEBUG_LIBRARIES}) - endif() - endif() - - if(CMAKE_BUILD_TYPE STREQUAL "Release") - if(WIN32 AND NOT SHIBOKEN_PYTHON_LIBRARIES) - set(SHIBOKEN_PYTHON_LIBRARIES ${PYTHON_LIBRARIES}) - endif() + if(WIN32 AND NOT SHIBOKEN_PYTHON_LIBRARIES) + set(SHIBOKEN_PYTHON_LIBRARIES ${Python_LIBRARIES}) endif() # If the resulting variable @@ -474,20 +458,20 @@ macro(shiboken_compute_python_libraries) endmacro() function(shiboken_check_if_built_and_target_python_are_compatible) - if(NOT SHIBOKEN_PYTHON_VERSION_MAJOR STREQUAL PYTHON_VERSION_MAJOR) + if(NOT SHIBOKEN_PYTHON_VERSION_MAJOR STREQUAL Python_VERSION_MAJOR) message(FATAL_ERROR "The detected Python major version is not \ compatible with the Python major version which was used when Shiboken was built. Built with: '${SHIBOKEN_PYTHON_VERSION_MAJOR}.${SHIBOKEN_PYTHON_VERSION_MINOR}' \ -Detected: '${PYTHON_VERSION_MAJOR}.${PYTHON_VERSION_MINOR}'") +Detected: '${Python_VERSION_MAJOR}.${Python_VERSION_MINOR}'") else() if(NOT SHIBOKEN_PYTHON_LIMITED_API - AND NOT SHIBOKEN_PYTHON_VERSION_MINOR STREQUAL PYTHON_VERSION_MINOR) + AND NOT SHIBOKEN_PYTHON_VERSION_MINOR STREQUAL Python_VERSION_MINOR) message(FATAL_ERROR "The detected Python minor version is not compatible with the Python minor \ version which was used when Shiboken was built. Consider building shiboken with \ FORCE_LIMITED_API set to '1', so that only the Python major version matters. Built with: '${SHIBOKEN_PYTHON_VERSION_MAJOR}.${SHIBOKEN_PYTHON_VERSION_MINOR}' \ -Detected: '${PYTHON_VERSION_MAJOR}.${PYTHON_VERSION_MINOR}'") +Detected: '${Python_VERSION_MAJOR}.${Python_VERSION_MINOR}'") endif() endif() endfunction() @@ -690,28 +674,10 @@ endmacro() # tool_name should be a unique tool name, preferably without spaces. # Returns the wrapper path in path_out_var. # -# Currently adds the Qt lib dir and libclang to PATH. +# Currently adds the Qt lib dir and libclang to PATH / LD_LIBRARY_PATH / DYLD_LIBRARY_PATH. # Meant to be used as the first argument to add_custom_command's COMMAND option. # TODO: Remove tool_name as the tool_name for this function is always shiboken. function(shiboken_get_tool_shell_wrapper tool_name path_out_var) - - # Make sure that for cross building, the host shiboken_wrapper.sh tool is used instead of target - # shiboken_wrapper.sh when calling shiboken. This wrapper script resolves the dependency to Qt - # libraries. - if((SHIBOKEN_IS_CROSS_BUILD OR PYSIDE_IS_CROSS_BUILD) AND - (CMAKE_HOST_UNIX AND NOT CMAKE_HOST_APPLE)) - set(host_tool_wrapper_path - "${QFP_SHIBOKEN_HOST_PATH}/../build/shiboken6/.qfp/bin/shiboken_wrapper.sh") - - if(EXISTS "${host_tool_wrapper_path}") - set_property(GLOBAL PROPERTY "_shiboken_tool_wrapper_shiboken_path" - "${host_tool_wrapper_path}") - set_property(GLOBAL PROPERTY "_shiboken_tool_wrapper_shiboken_created" TRUE) - else() - message(FATAL_ERROR "${host_tool_wrapper_path} does not exist") - endif() - endif() - # Generate the wrapper only once during the execution of CMake. get_property(is_called GLOBAL PROPERTY "_shiboken_tool_wrapper_${tool_name}_created") @@ -725,25 +691,56 @@ function(shiboken_get_tool_shell_wrapper tool_name path_out_var) set(path_dirs_native "") if(CMAKE_HOST_WIN32) + set(wrapper_script_extension ".bat") + else() + set(wrapper_script_extension ".sh") + endif() + + # Try to get original host shiboken paths from exported target properties. + shiboken_get_host_tool_wrapper_properties(orig_qt_library_dir_absolute orig_libclang_lib_dir) + + # Get path to the Qt bin/lib dir depending on the platform and developer input. + # Prefer values given on the command line, then the original host path if it exists, otherwise + # try to use the Qt install prefix and libclang env vars. + # + # Note that in a cross-compiling case, using the Qt install prefix is very likely + # wrong, because you want to use the location of the host Qt, not the target Qt. Same for + # libclang. Unfortunately we currently don't provide a host Qt and host libclang option via + # setup.py, so the manual cmake vars will have to suffice. + if(SHIBOKEN_WRAPPER_HOST_QT_LIB_PATH AND EXISTS "${SHIBOKEN_WRAPPER_HOST_QT_LIB_PATH}") + set(qt_library_dir_absolute "${SHIBOKEN_WRAPPER_HOST_QT_LIB_PATH}") + elseif(orig_qt_library_dir_absolute AND EXISTS "${orig_qt_library_dir_absolute}") + set(qt_library_dir_absolute "${orig_qt_library_dir_absolute}") + elseif(CMAKE_HOST_WIN32) # in Windows the Qt dll are store `bin` in directory set(qt_library_dir ${QT6_INSTALL_BINS}) - set(wrapper_script_extension ".bat") else() # in Unix the .so are stored in `lib` directory set(qt_library_dir ${QT6_INSTALL_LIBS}) - set(wrapper_script_extension ".sh") endif() # Assert that Qt is already found. - if(NOT QT6_INSTALL_PREFIX OR NOT qt_library_dir) + if((QT6_INSTALL_PREFIX AND qt_library_dir) OR orig_qt_library_dir_absolute) + else() message(FATAL_ERROR "Qt should have been found already by now.") endif() - # Get path to the Qt bin/lib dir depending on the platform - list(APPEND path_dirs "${QT6_INSTALL_PREFIX}/${qt_library_dir}") + if(NOT qt_library_dir_absolute) + set(qt_library_dir_absolute "${QT6_INSTALL_PREFIX}/${qt_library_dir}") + endif() + list(APPEND path_dirs "${qt_library_dir_absolute}") + + # Get libclang lib dir path. + # Prefer values given on the command line, then the original host path if it exists. + if(SHIBOKEN_WRAPPER_HOST_CLANG_LIB_PATH AND EXISTS "${SHIBOKEN_WRAPPER_HOST_CLANG_LIB_PATH}") + set(libclang_lib_dir "${SHIBOKEN_WRAPPER_HOST_CLANG_LIB_PATH}") + elseif(orig_libclang_lib_dir AND EXISTS "${orig_libclang_lib_dir}") + set(libclang_lib_dir "${orig_libclang_lib_dir}") + else() + # find libclang + find_libclang() + endif() - # find libclang - find_libclang() if(libclang_lib_dir) list(APPEND path_dirs "${libclang_lib_dir}") endif() @@ -781,6 +778,9 @@ $@") set_property(GLOBAL PROPERTY "_shiboken_tool_wrapper_${tool_name}_path" "${wrapper_path}") set_property(GLOBAL PROPERTY "_shiboken_tool_wrapper_${tool_name}_created" TRUE) + # Save original host paths for future cross-builds. + shiboken_save_host_tool_wrapper_properties("${qt_library_dir_absolute}" "${libclang_lib_dir}") + # give execute permission to run the file if(CMAKE_HOST_UNIX) execute_process(COMMAND chmod +x ${wrapper_path}) @@ -789,6 +789,48 @@ $@") set(${path_out_var} "${wrapper_path}" PARENT_SCOPE) endfunction() +# Retrieve the original host shiboken runtime dependency paths from the installed (namespaced) +# shiboken generator target. +function(shiboken_get_host_tool_wrapper_properties out_qt_library_dir out_libclang_lib_dir) + if(TARGET Shiboken6::shiboken6) + get_target_property(qt_library_dir Shiboken6::shiboken6 _shiboken_original_qt_lib_dir) + if(NOT qt_library_dir) + set(qt_library_dir "") + endif() + get_target_property(libclang_lib_dir Shiboken6::shiboken6 + _shiboken_original_libclang_lib_dir) + if(NOT libclang_lib_dir) + set(libclang_lib_dir "") + endif() + endif() + + set(${out_qt_library_dir} "${qt_library_dir}" PARENT_SCOPE) + set(${out_libclang_lib_dir} "${libclang_lib_dir}" PARENT_SCOPE) +endfunction() + +# Save original host shiboken runtime dependency paths as target properties, so they can be used +# when generating the wrapper file for cross-builds. +# Should only be done when shiboken is being built (aka it's a non-imported target). +function(shiboken_save_host_tool_wrapper_properties qt_library_dir libclang_lib_dir) + if(TARGET shiboken6) + get_target_property(is_imported shiboken6 IMPORTED) + if(is_imported) + return() + endif() + + set_target_properties(shiboken6 PROPERTIES + _shiboken_original_qt_lib_dir "${qt_library_dir}") + set_property(TARGET shiboken6 APPEND PROPERTY + EXPORT_PROPERTIES _shiboken_original_qt_lib_dir) + if(libclang_lib_dir) + set_target_properties(shiboken6 PROPERTIES + _shiboken_original_libclang_lib_dir "${libclang_lib_dir}") + set_property(TARGET shiboken6 APPEND PROPERTY + EXPORT_PROPERTIES _shiboken_original_libclang_lib_dir) + endif() + endif() +endfunction() + # Returns the platform-specific relative rpath base token, if it's supported. # If it's not supported, returns the string NO_KNOWN_RPATH_REL_BASE. function(get_rpath_base_token out_var) diff --git a/sources/shiboken6/cmake/ShibokenSetup.cmake b/sources/shiboken6/cmake/ShibokenSetup.cmake index c1d477be..32823d9f 100644 --- a/sources/shiboken6/cmake/ShibokenSetup.cmake +++ b/sources/shiboken6/cmake/ShibokenSetup.cmake @@ -7,7 +7,6 @@ list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}") include(ShibokenHelpers) -option(USE_PYTHON_VERSION "Use specific python version to build shiboken6." "") option(DISABLE_DOCSTRINGS "Disable documentation extraction." FALSE) shiboken_internal_disable_pkg_config_if_needed() @@ -44,13 +43,12 @@ set(shiboken6_library_so_version "${shiboken_MAJOR_VERSION}.${shiboken_MINOR_VER compute_config_py_values(shiboken6_VERSION) ## For debugging the PYTHON* variables -message(STATUS "PYTHONLIBS_FOUND: " ${PYTHONLIBS_FOUND}) -message(STATUS "PYTHON_LIBRARIES: " ${PYTHON_LIBRARIES}) -message(STATUS "PYTHON_INCLUDE_DIRS: " ${PYTHON_INCLUDE_DIRS}) -message(STATUS "PYTHON_DEBUG_LIBRARIES: " ${PYTHON_DEBUG_LIBRARIES}) -message(STATUS "PYTHONINTERP_FOUND: " ${PYTHONINTERP_FOUND}) -message(STATUS "PYTHON_EXECUTABLE: " ${PYTHON_EXECUTABLE}) -message(STATUS "PYTHON_VERSION: " ${PYTHON_VERSION_MAJOR}.${PYTHON_VERSION_MINOR}.${PYTHON_VERSION_PATCH}) +message(STATUS "Python_Development_FOUND: " ${Python_Development_FOUND}) +message(STATUS "Python_LIBRARIES: " ${Python_LIBRARIES}) +message(STATUS "Python_INCLUDE_DIRS: " ${Python_INCLUDE_DIRS}) +message(STATUS "Python_Interpreter_FOUND: " ${Python_Interpreter_FOUND}) +message(STATUS "Python_EXECUTABLE: " ${Python_EXECUTABLE}) +message(STATUS "Python_VERSION: " ${Python_VERSION_MAJOR}.${Python_VERSION_MINOR}.${Python_VERSION_PATCH}) if(NOT PYTHON_EXTENSION_SUFFIX) get_python_extension_suffix() @@ -121,7 +119,7 @@ endif() # Detect if the python libs were compiled in debug mode # On Linux distros there is no standard way to check that. execute_process( - COMMAND ${PYTHON_EXECUTABLE} -c "if True: + COMMAND ${Python_EXECUTABLE} -c "if True: import sys import sysconfig config_py_debug = sysconfig.get_config_var('Py_DEBUG') @@ -139,7 +137,7 @@ if(SHIBOKEN_IS_CROSS_BUILD) set(PYTHON_WITH_COUNT_ALLOCS 0) else() execute_process( - COMMAND ${PYTHON_EXECUTABLE} -c "if True: + COMMAND ${Python_EXECUTABLE} -c "if True: count_allocs = False import sys try: diff --git a/sources/shiboken6/data/Shiboken6Config-spec.cmake.in b/sources/shiboken6/data/Shiboken6Config-spec.cmake.in index a07563a5..233404bc 100644 --- a/sources/shiboken6/data/Shiboken6Config-spec.cmake.in +++ b/sources/shiboken6/data/Shiboken6Config-spec.cmake.in @@ -5,9 +5,9 @@ # This is the version of Python against which Shiboken was built. Not necessarily the version # against which a downstream project is built (e.g. PySide6). -set(SHIBOKEN_PYTHON_VERSION_MAJOR "@PYTHON_VERSION_MAJOR@") -set(SHIBOKEN_PYTHON_VERSION_MINOR "@PYTHON_VERSION_MINOR@") -set(SHIBOKEN_PYTHON_VERSION_PATCH "@PYTHON_VERSION_PATCH@") +set(SHIBOKEN_PYTHON_VERSION_MAJOR "@Python_VERSION_MAJOR@") +set(SHIBOKEN_PYTHON_VERSION_MINOR "@Python_VERSION_MINOR@") +set(SHIBOKEN_PYTHON_VERSION_PATCH "@Python_VERSION_PATCH@") set(SHIBOKEN_PYTHON_LIMITED_API "@PYTHON_LIMITED_API@") # Import targets and call variable set up functions only when using an installed shiboken config @@ -17,7 +17,7 @@ if (NOT TARGET Shiboken6::libshiboken) include("${CMAKE_CURRENT_LIST_DIR}/ShibokenHelpers.cmake") # Compute the python include and libraries path if needed (aka not part of super project build). - shiboken_find_required_python(@PYTHON_VERSION_MAJOR@) + shiboken_find_required_python(@Python_VERSION_MAJOR@) shiboken_check_if_built_and_target_python_are_compatible() shiboken_check_if_limited_api() shiboken_compute_python_includes(IS_CALLED_FROM_EXPORT) diff --git a/sources/shiboken6/data/shiboken6.pc.in b/sources/shiboken6/data/shiboken6.pc.in index 3ba42262..a82d2316 100644 --- a/sources/shiboken6/data/shiboken6.pc.in +++ b/sources/shiboken6/data/shiboken6.pc.in @@ -2,11 +2,11 @@ prefix=@CMAKE_INSTALL_PREFIX@ exec_prefix=@CMAKE_INSTALL_PREFIX@ libdir=@CMAKE_INSTALL_PREFIX@/@LIB_INSTALL_DIR@ includedir=@CMAKE_INSTALL_PREFIX@/include/shiboken6 -python_interpreter=@PYTHON_EXECUTABLE@ -python_include_dir=@PYTHON_INCLUDE_DIRS@ +python_interpreter=@Python_EXECUTABLE@ +python_include_dir=@Python_INCLUDE_DIRS@ Name: shiboken6 Description: Support library for Python bindings created with the Shiboken6 generator. Version: @shiboken6_VERSION@ Libs: @SHIBOKEN_PYTHON_LIBRARIES@ -L${libdir} -lshiboken6@shiboken6_SUFFIX@@PYTHON_SHARED_LIBRARY_SUFFIX@@LIBRARY_OUTPUT_SUFFIX@ -Cflags: -I@PYTHON_INCLUDE_DIRS@ -I${includedir}/@shiboken6_SUFFIX@@SBK_PKG_CONFIG_PY_DEBUG_DEFINITION@ +Cflags: -I@Python_INCLUDE_DIRS@ -I${includedir}/@shiboken6_SUFFIX@@SBK_PKG_CONFIG_PY_DEBUG_DEFINITION@ diff --git a/sources/shiboken6/doc/CMakeLists.txt b/sources/shiboken6/doc/CMakeLists.txt index 74987703..eaef4ff2 100644 --- a/sources/shiboken6/doc/CMakeLists.txt +++ b/sources/shiboken6/doc/CMakeLists.txt @@ -37,7 +37,7 @@ if(SPHINX_BUILD) if(SHIBOKEN_IS_CROSS_BUILD) set(python_executable "${QFP_PYTHON_HOST_PATH}") else() - set(python_executable "${PYTHON_EXECUTABLE}") + set(python_executable "${Python_EXECUTABLE}") endif() if(NOT python_executable OR NOT EXISTS "${python_executable}") message(FATAL_ERROR "No python executable found to build documentation.") diff --git a/sources/shiboken6/doc/conf.py.in b/sources/shiboken6/doc/conf.py.in index 23bad0d7..b10f33b2 100644 --- a/sources/shiboken6/doc/conf.py.in +++ b/sources/shiboken6/doc/conf.py.in @@ -73,7 +73,7 @@ master_doc = 'index' # General information about the project. project = u'Shiboken' -copyright = u'2021 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 (https://www.gnu.org/licenses/fdl.html) as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.' +copyright = u'2024 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 (https://www.gnu.org/licenses/fdl.html) as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. diff --git a/sources/shiboken6/doc/shibokengenerator.rst b/sources/shiboken6/doc/shibokengenerator.rst index dde5fca6..14340ab6 100644 --- a/sources/shiboken6/doc/shibokengenerator.rst +++ b/sources/shiboken6/doc/shibokengenerator.rst @@ -322,6 +322,9 @@ QtDocGenerator Options ``--inheritance-file=`` Generate a JSON file containing the class inheritance. +``--disable-inheritance-diagram`` + Disable the generation of the inheritance diagram. + .. _project-file: ******************** diff --git a/sources/shiboken6/doc/typesystem_specifying_types.rst b/sources/shiboken6/doc/typesystem_specifying_types.rst index 7152c70a..406d5165 100644 --- a/sources/shiboken6/doc/typesystem_specifying_types.rst +++ b/sources/shiboken6/doc/typesystem_specifying_types.rst @@ -205,6 +205,8 @@ child nodes. generate-using="yes | no" package="..." since="..." + extends = "..." + files = "..." revision="..." /> @@ -235,6 +237,16 @@ The *optional* **since** value is used to specify the API version of this type. The **revision** attribute can be used to specify a revision for each type, easing the production of ABI compatible bindings. +The *optional* **extends** attribute specifies the module name where the given +namespace first occurs in case of a namespace spanning several modules. For +example, in Qt, the namespace ``Qt`` first occurs in the ``QtCore`` module and +is further populated in the ``QtGui`` module. ``QtGui.Qt`` will then be +generated extending ``QtCore.Qt`` if **extends** is specified. + +The *optional* **file** attribute specifies a regular expression matching the +include files whose contents are to be associated with the current module in +case of a namespace spanning several modules. + .. _enum-type: enum-type diff --git a/sources/shiboken6/generator/CMakeLists.txt b/sources/shiboken6/generator/CMakeLists.txt index 466de432..aebe2cd5 100644 --- a/sources/shiboken6/generator/CMakeLists.txt +++ b/sources/shiboken6/generator/CMakeLists.txt @@ -6,7 +6,7 @@ set(package_name "Shiboken6Tools") set(CMAKE_AUTOMOC ON) -if(NOT (Qt${QT_MAJOR_VERSION}Core_FOUND AND PYTHONINTERP_FOUND)) +if(NOT (Qt${QT_MAJOR_VERSION}Core_FOUND AND Python_Interpreter_FOUND)) message(WARNING "Some dependencies were not found: shiboken6 generator compilation disabled!") return() endif() diff --git a/sources/shiboken6/generator/generator.cpp b/sources/shiboken6/generator/generator.cpp index 9043550b..7785b8f9 100644 --- a/sources/shiboken6/generator/generator.cpp +++ b/sources/shiboken6/generator/generator.cpp @@ -386,7 +386,7 @@ std::optional if (type.isNativePointer()) return DefaultValue(DefaultValue::Pointer, type.typeEntry()->qualifiedCppName()); if (type.isPointer()) - return DefaultValue(DefaultValue::Pointer, u"::"_s + type.typeEntry()->qualifiedCppName()); + return DefaultValue(DefaultValue::Pointer, getFullTypeName(type.typeEntry())); if (type.typeEntry()->isSmartPointer()) return minimalConstructor(api, type.typeEntry()); @@ -435,8 +435,7 @@ std::optional if (const auto nullValue = enumEntry->nullValue()) return DefaultValue(DefaultValue::Enum, nullValue->name()); return DefaultValue(DefaultValue::Custom, - u"static_cast< ::"_s + type->qualifiedCppName() - + u">(0)"_s); + "static_cast< "_L1 + getFullTypeName(type) + ">(0)"_L1); } if (type->isFlags()) { diff --git a/sources/shiboken6/generator/qtdoc/qtdocgenerator.cpp b/sources/shiboken6/generator/qtdoc/qtdocgenerator.cpp index ef61a077..bba60fa9 100644 --- a/sources/shiboken6/generator/qtdoc/qtdocgenerator.cpp +++ b/sources/shiboken6/generator/qtdoc/qtdocgenerator.cpp @@ -15,6 +15,7 @@ #include #include #include +#include "abstractmetalang_helpers.h" #include #include #include @@ -25,6 +26,8 @@ #include #include #include +#include +#include #include #include @@ -36,6 +39,7 @@ #include #include #include +#include #include #include @@ -49,6 +53,7 @@ struct DocGeneratorOptions QString additionalDocumentationList; QString inheritanceFile; bool doxygen = false; + bool inheritanceDiagram = true; }; struct GeneratorDocumentation @@ -288,6 +293,18 @@ void QtDocGenerator::writeFormattedText(TextStream &s, const QString &doc, s << '\n'; } +static void writeInheritanceList(TextStream &s, const AbstractMetaClassCList& classes, + const char *label) +{ + s << "**" << label << ":** "; + for (qsizetype i = 0, size = classes.size(); i < size; ++i) { + if (i > 0) + s << ", "; + s << ":ref:`" << classes.at(i)->name() << '`'; + } + s << "\n\n"; +} + static void writeInheritedByList(TextStream &s, const AbstractMetaClassCPtr &metaClass, const AbstractMetaClassCList& allClasses) { @@ -297,14 +314,22 @@ static void writeInheritedByList(TextStream &s, const AbstractMetaClassCPtr &met res << c; } - if (res.isEmpty()) - return; + if (!res.isEmpty()) + writeInheritanceList(s, res, "Inherited by"); +} + +static void writeInheritedFromList(TextStream &s, const AbstractMetaClassCPtr &metaClass) +{ + AbstractMetaClassCList res; - s << "**Inherited by:** "; - QStringList classes; - for (const auto &c : std::as_const(res)) - classes << u":ref:`"_s + c->name() + u'`'; - s << classes.join(u", "_s) << "\n\n"; + recurseClassHierarchy(metaClass, [&res, metaClass](const AbstractMetaClassCPtr &c) { + if (c.get() != metaClass.get()) + res.append(c); + return false; + }); + + if (!res.isEmpty()) + writeInheritanceList(s, res, "Inherits from"); } void QtDocGenerator::generateClass(TextStream &s, const GeneratorContext &classContext) @@ -328,10 +353,14 @@ void QtDocGenerator::generateClass(TextStream &s, const GeneratorContext &classC if (documentation.hasBrief()) writeFormattedBriefText(s, documentation, metaClass); - s << ".. inheritance-diagram:: " << metaClass->fullName()<< '\n' - << " :parts: 2\n\n"; - // TODO: This would be a parameter in the future... - + if (!metaClass->baseClasses().isEmpty()) { + if (m_options.inheritanceDiagram) { + s << ".. inheritance-diagram:: " << metaClass->fullName()<< '\n' + << " :parts: 2\n\n"; + } else { + writeInheritedFromList(s, metaClass); + } + } writeInheritedByList(s, metaClass, api().classes()); @@ -681,6 +710,11 @@ QString QtDocGenerator::functionSignature(const AbstractMetaClassCPtr &cppClass, return funcName + formatArgs(func); } +static QString inline toRef(const QString &t) +{ + return ":any:`"_L1 + t + u'`'; +} + QString QtDocGenerator::translateToPythonType(const AbstractMetaType &type, const AbstractMetaClassCPtr &cppClass, bool createRef) const @@ -688,36 +722,47 @@ QString QtDocGenerator::translateToPythonType(const AbstractMetaType &type, static const QStringList nativeTypes = {boolT(), floatT(), intT(), pyObjectT(), pyStrT()}; - const QString name = type.name(); + QString name = type.name(); if (nativeTypes.contains(name)) return name; - static const QMap typeMap = { + if (type.typeUsagePattern() == AbstractMetaType::PrimitivePattern) { + const auto &basicName = basicReferencedTypeEntry(type.typeEntry())->name(); + if (AbstractMetaType::cppSignedIntTypes().contains(basicName) + || AbstractMetaType::cppUnsignedIntTypes().contains(basicName)) { + return intT(); + } + if (AbstractMetaType::cppFloatTypes().contains(basicName)) + return floatT(); + } + + static const QSet stringTypes = { + u"uchar"_s, u"std::string"_s, u"std::wstring"_s, + u"std::stringview"_s, u"std::wstringview"_s, + qStringT(), u"QStringView"_s, u"QAnyStringView"_s, u"QUtf8StringView"_s + }; + if (stringTypes.contains(name)) + return pyStrT(); + + static const QHash typeMap = { { cPyObjectT(), pyObjectT() }, - { qStringT(), pyStrT() }, - { u"uchar"_s, pyStrT() }, { u"QStringList"_s, u"list of strings"_s }, - { qVariantT(), pyObjectT() }, - { u"quint32"_s, intT() }, - { u"uint32_t"_s, intT() }, - { u"quint64"_s, intT() }, - { u"qint64"_s, intT() }, - { u"size_t"_s, intT() }, - { u"int64_t"_s, intT() }, - { u"qreal"_s, floatT() } + { qVariantT(), pyObjectT() } }; - const auto found = typeMap.find(name); - if (found != typeMap.end()) + const auto found = typeMap.constFind(name); + if (found != typeMap.cend()) return found.value(); - QString strType; - if (type.isConstant() && name == u"char" && type.indirections() == 1) { - strType = u"str"_s; - } else if (name.startsWith(unsignedShortT())) { - strType = intT(); - } else if (name.startsWith(unsignedT())) { // uint and ulong - strType = intT(); - } else if (type.isContainer()) { + if (type.isFlags()) { + const auto fte = std::static_pointer_cast(type.typeEntry()); + auto enumName = fte->originator()->targetLangName(); + return "Combination of "_L1 + (createRef ? toRef(enumName) : enumName); + } + + if (type.isConstant() && name == "char"_L1 && type.indirections() == 1) + return "str"_L1; + + if (type.isContainer()) { QString strType = translateType(type, cppClass, Options(ExcludeConst) | ExcludeReference); strType.remove(u'*'); strType.remove(u'>'); @@ -733,15 +778,13 @@ QString QtDocGenerator::translateToPythonType(const AbstractMetaType &type, strType = QString::fromLatin1("Dictionary with keys of type %1 and values of type %2.") .arg(types[0], types[1]); } - } else { - auto k = AbstractMetaClass::findClass(api().classes(), type.typeEntry()); - strType = k ? k->fullName() : type.name(); - if (createRef) { - strType.prepend(u":any:`"_s); - strType.append(u'`'); - } + return strType; } - return strType; + + if (auto k = AbstractMetaClass::findClass(api().classes(), type.typeEntry())) + return createRef ? toRef(k->fullName()) : k->fullName(); + + return createRef ? toRef(name) : name; } QString QtDocGenerator::getFuncName(const AbstractMetaFunctionCPtr &cppFunc) @@ -1123,7 +1166,9 @@ QList QtDocGenerator::options() u"List of additional XML files to be converted to .rst files\n" "(for example, tutorials)."_s}, {u"inheritance-file="_s, - u"Generate a JSON file containing the class inheritance."_s} + u"Generate a JSON file containing the class inheritance."_s}, + {u"disable-inheritance-diagram"_s, + u"Disable the generation of the inheritance diagram."_s} }; } @@ -1132,12 +1177,22 @@ class QtDocGeneratorOptionsParser : public OptionsParser public: explicit QtDocGeneratorOptionsParser(DocGeneratorOptions *o) : m_options(o) {} + bool handleBoolOption(const QString &key, OptionSource source) override; bool handleOption(const QString &key, const QString &value, OptionSource source) override; private: DocGeneratorOptions *m_options; }; +bool QtDocGeneratorOptionsParser::handleBoolOption(const QString &key, OptionSource) +{ + if (key == "disable-inheritance-diagram"_L1) { + m_options->inheritanceDiagram = false; + return true; + } + return false; +} + bool QtDocGeneratorOptionsParser::handleOption(const QString &key, const QString &value, OptionSource source) { diff --git a/sources/shiboken6/generator/qtdoc/qtxmltosphinx.cpp b/sources/shiboken6/generator/qtdoc/qtxmltosphinx.cpp index 2fd01621..b129a453 100644 --- a/sources/shiboken6/generator/qtdoc/qtxmltosphinx.cpp +++ b/sources/shiboken6/generator/qtdoc/qtxmltosphinx.cpp @@ -945,7 +945,7 @@ void QtXmlToSphinx::handleSnippetTag(QXmlStreamReader& reader) || m_lastTagName == u"dots" || m_lastTagName == u"codeline"; if (consecutiveSnippet) { m_output.flush(); - m_output.string()->chop(2); + m_output.string()->chop(1); // Strip newline from previous snippet } QString location = reader.attributes().value(u"location"_s).toString(); QString identifier = reader.attributes().value(u"identifier"_s).toString(); diff --git a/sources/shiboken6/generator/shiboken/cppgenerator.cpp b/sources/shiboken6/generator/shiboken/cppgenerator.cpp index 74a9d8e9..1f3c68dd 100644 --- a/sources/shiboken6/generator/shiboken/cppgenerator.cpp +++ b/sources/shiboken6/generator/shiboken/cppgenerator.cpp @@ -455,6 +455,19 @@ void CppGenerator::writePyMethodDefs(TextStream &s, const QString &className, << methodsDefinitions << METHOD_DEF_SENTINEL << outdent << "};\n\n"; } +void CppGenerator::writeModuleCodeSnips(TextStream &s, const CodeSnipList &codeSnips, + TypeSystem::CodeSnipPosition position, + TypeSystem::Language language) const +{ + if (!codeSnips.isEmpty()) { + try { + writeCodeSnips(s, codeSnips, position, language); + } catch (const std::exception &e) { + throw Exception(msgSnippetError("module source of "_L1 + moduleName(), e.what())); + } + } +} + bool CppGenerator::hasHashFunction(const AbstractMetaClassCPtr &c) { return !c->typeEntry()->hashFunction().isEmpty() @@ -1338,7 +1351,7 @@ void CppGenerator::writeVirtualMethodNative(TextStream &s, if (isProtectedEnum) { QString typeCast; if (metaEnum->enclosingClass()) - typeCast += u"::"_s + metaEnum->enclosingClass()->qualifiedCppName(); + typeCast += getFullTypeName(metaEnum->enclosingClass()); typeCast += u"::"_s + metaEnum->name(); s << '(' << typeCast << ')'; } @@ -1430,7 +1443,8 @@ static void generateDeprecatedValueWarnings(TextStream &c, << "\", \"" << v.name() << "\");\nbreak;\n" << outdent; } if (deprecatedValues.size() < metaEnum.values().size()) - c << "default:\n" << indent << "break;\n" << outdent << "}\n"; + c << "default:\n" << indent << "break;\n" << outdent; + c << "}\n"; } void CppGenerator::writeEnumConverterFunctions(TextStream &s, const AbstractMetaEnum &metaEnum) const @@ -2295,8 +2309,7 @@ void CppGenerator::writeCppSelfDefinition(TextStream &s, && cppWrapper.testFlag(AbstractMetaClass::CppProtectedHackWrapper); Q_ASSERT(!useWrapperClass || context.useWrapper()); const QString className = useWrapperClass - ? context.wrapperName() - : (u"::"_s + metaClass->qualifiedCppName()); + ? context.wrapperName() : getFullTypeName(metaClass); writeInvalidPyObjectCheck(s, u"self"_s, errorReturn); @@ -3097,9 +3110,10 @@ void CppGenerator::writeCppToPythonFunction(TextStream &s, const QString &code, { QString prettyCode = code; - processCodeSnip(prettyCode); + const QString funcName = cppToPythonFunctionName(sourceTypeName, targetTypeName); + processCodeSnip(prettyCode, funcName); - s << "static PyObject *" << cppToPythonFunctionName(sourceTypeName, targetTypeName) + s << "static PyObject *" << funcName << "(const void *cppIn)\n{\n" << indent << prettyCode << ensureEndl << outdent << "}\n"; } @@ -3170,7 +3184,7 @@ void CppGenerator::writeCppToPythonFunction(TextStream &s, const AbstractMetaTyp code.replace(u"%INTYPE_"_s + QString::number(i), typeName); } replaceCppToPythonVariables(code, getFullTypeNameWithoutModifiers(containerType), true); - processCodeSnip(code); + processCodeSnip(code, containerType.typeEntry()->qualifiedCppName()); writeCppToPythonFunction(s, code, fixedCppTypeName(containerType), containerNativeToTargetTypeName(cte)); } @@ -3179,8 +3193,9 @@ void CppGenerator::writePythonToCppFunction(TextStream &s, const QString &code, const QString &targetTypeName) const { QString prettyCode = code; - processCodeSnip(prettyCode); - s << "static void " << pythonToCppFunctionName(sourceTypeName, targetTypeName) + const QString funcName = pythonToCppFunctionName(sourceTypeName, targetTypeName); + processCodeSnip(prettyCode, funcName); + s << "static void " << funcName << "(PyObject *pyIn, void *cppOut)\n{\n" << indent << prettyCode << ensureEndl << outdent << "}\n"; } @@ -3283,7 +3298,7 @@ void CppGenerator::writePythonToCppConversionFunctions(TextStream &s, + cpythonTypeNameExt(toNative.sourceType()) + u')'; } typeCheck.replace(u"%in"_s, u"pyIn"_s); - processCodeSnip(typeCheck); + processCodeSnip(typeCheck, targetType->qualifiedCppName()); writeIsPythonConvertibleToCppFunction(s, sourceTypeName, targetTypeName, typeCheck); } @@ -3756,7 +3771,8 @@ void CppGenerator::writeMethodCall(TextStream &s, const AbstractMetaFunctionCPtr if (!func->isStatic()) { const bool directInheritance = context.metaClass() == ownerClass; mc << (directInheritance ? "static_cast" : "reinterpret_cast") - << "<::" << wrapperName(ownerClass) << " *>(" << CPP_SELF_VAR << ")->"; + << '<' << wrapperName(ownerClass) << " *>(" + << CPP_SELF_VAR << ")->"; } if (!func->isAbstract()) @@ -4025,7 +4041,7 @@ void CppGenerator::writeSpecialCastFunction(TextStream &s, const AbstractMetaCla s << "else "; s << "if (desiredType == " << cpythonTypeNameExt(baseClass->typeEntry()) << ")\n" << indent - << "return static_cast< ::" << baseClass->qualifiedCppName() << " *>(me);\n" + << "return static_cast< " << getFullTypeName(baseClass) << " *>(me);\n" << outdent; firstClass = false; } @@ -5488,12 +5504,10 @@ void CppGenerator::writeClassRegister(TextStream &s, if (usePySideExtensions() && isQObject(metaClass)) { s << "Shiboken::ObjectType::setSubTypeInitHook(pyType, &PySide::initQObjectSubType);\n" << "PySide::initDynamicMetaObject(pyType, &::" - << metaClass->qualifiedCppName() << "::staticMetaObject, sizeof("; - if (shouldGenerateCppWrapper(metaClass)) - s << wrapperName(metaClass); - else - s << "::" << metaClass->qualifiedCppName(); - s << "));\n"; + << metaClass->qualifiedCppName() << "::staticMetaObject, sizeof(" + << (shouldGenerateCppWrapper(metaClass) + ? wrapperName(metaClass) : getFullTypeName(metaClass)) + << "));\n"; } s << outdent << "}\n"; @@ -5636,8 +5650,8 @@ void CppGenerator::writeTypeDiscoveryFunction(TextStream &s, if (ancestor->isPolymorphic()) { s << "if (instanceType == Shiboken::SbkType< ::" << ancestor->qualifiedCppName() << " >())\n" << indent - << "return dynamic_cast< ::" << metaClass->qualifiedCppName() - << " *>(reinterpret_cast< ::"<< ancestor->qualifiedCppName() + << "return dynamic_cast< " << getFullTypeName(metaClass) + << " *>(reinterpret_cast< "<< getFullTypeName(ancestor) << " *>(cptr));\n" << outdent; } else { qCWarning(lcShiboken).noquote().nospace() @@ -6006,8 +6020,7 @@ bool CppGenerator::finishGeneration() const CodeSnipList snips = moduleEntry->codeSnips(); // module inject-code native/beginning - if (!snips.isEmpty()) - writeCodeSnips(s, snips, TypeSystem::CodeSnipPositionBeginning, TypeSystem::NativeCode); + writeModuleCodeSnips(s, snips, TypeSystem::CodeSnipPositionBeginning, TypeSystem::NativeCode); // cleanup staticMetaObject attribute if (usePySideExtensions()) { @@ -6137,8 +6150,8 @@ bool CppGenerator::finishGeneration() << indent << "return " << globalModuleVar << ";\n" << outdent; // module inject-code target/beginning - if (!snips.isEmpty()) - writeCodeSnips(s, snips, TypeSystem::CodeSnipPositionBeginning, TypeSystem::TargetLangCode); + writeModuleCodeSnips(s, snips, TypeSystem::CodeSnipPositionBeginning, + TypeSystem::TargetLangCode); for (const QString &requiredModule : requiredModules) { s << "{\n" << indent @@ -6257,18 +6270,16 @@ bool CppGenerator::finishGeneration() << outdent << "}\n"; // module inject-code target/end - if (!snips.isEmpty()) - writeCodeSnips(s, snips, TypeSystem::CodeSnipPositionEnd, TypeSystem::TargetLangCode); + writeModuleCodeSnips(s, snips, TypeSystem::CodeSnipPositionEnd, TypeSystem::TargetLangCode); // module inject-code native/end - if (!snips.isEmpty()) - writeCodeSnips(s, snips, TypeSystem::CodeSnipPositionEnd, TypeSystem::NativeCode); + writeModuleCodeSnips(s, snips, TypeSystem::CodeSnipPositionEnd, TypeSystem::NativeCode); if (usePySideExtensions()) { for (const AbstractMetaEnum &metaEnum : std::as_const(globalEnums)) if (!metaEnum.isAnonymous()) { ConfigurableScope configScope(s, metaEnum.typeEntry()); - s << "qRegisterMetaType< ::" << metaEnum.typeEntry()->qualifiedCppName() + s << "qRegisterMetaType< " << getFullTypeName(metaEnum.typeEntry()) << " >(\"" << metaEnum.name() << "\");\n"; } diff --git a/sources/shiboken6/generator/shiboken/cppgenerator.h b/sources/shiboken6/generator/shiboken/cppgenerator.h index 54b37fad..95ed0bf7 100644 --- a/sources/shiboken6/generator/shiboken/cppgenerator.h +++ b/sources/shiboken6/generator/shiboken/cppgenerator.h @@ -521,6 +521,10 @@ private: static void writePyMethodDefs(TextStream &s, const QString &className, const QString &methodsDefinitions); + void writeModuleCodeSnips(TextStream &s, const CodeSnipList &codeSnips, + TypeSystem::CodeSnipPosition position, + TypeSystem::Language language) const; + static bool hasBoolCast(const AbstractMetaClassCPtr &metaClass) { return boolCast(metaClass).has_value(); } diff --git a/sources/shiboken6/generator/shiboken/headergenerator.cpp b/sources/shiboken6/generator/shiboken/headergenerator.cpp index 61ebfe8f..9f6de356 100644 --- a/sources/shiboken6/generator/shiboken/headergenerator.cpp +++ b/sources/shiboken6/generator/shiboken/headergenerator.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -20,6 +21,7 @@ #include "containertypeentry.h" #include "enumtypeentry.h" #include "flagstypeentry.h" +#include #include "namespacetypeentry.h" #include "primitivetypeentry.h" #include "typedefentry.h" @@ -593,10 +595,8 @@ bool HeaderGenerator::finishGeneration() StringStream macrosStream(TextStream::Language::Cpp); const auto snips = TypeDatabase::instance()->defaultTypeSystemType()->codeSnips(); - if (!snips.isEmpty()) { - writeCodeSnips(macrosStream, snips, TypeSystem::CodeSnipPositionDeclaration, - TypeSystem::TargetLangCode); - } + writeModuleCodeSnips(macrosStream, snips, TypeSystem::CodeSnipPositionDeclaration, + TypeSystem::TargetLangCode); macrosStream << "// Type indices\nenum : int {\n"; auto classList = api().classes(); @@ -880,8 +880,10 @@ void HeaderGenerator::writeSbkTypeFunction(TextStream &s, const AbstractMetaEnum void HeaderGenerator::writeSbkTypeFunction(TextStream &s, const AbstractMetaClassCPtr &cppClass) { - s << "template<> inline PyTypeObject *SbkType< ::" << cppClass->qualifiedCppName() << " >() " - << "{ return reinterpret_cast(" << cpythonTypeNameExt(cppClass->typeEntry()) << "); }\n"; + s << "template<> inline PyTypeObject *SbkType< " + << getFullTypeName(cppClass) << " >() " + << "{ return reinterpret_cast(" + << cpythonTypeNameExt(cppClass->typeEntry()) << "); }\n"; } void HeaderGenerator::writeSbkTypeFunction(TextStream &s, const AbstractMetaType &metaType) @@ -889,3 +891,16 @@ void HeaderGenerator::writeSbkTypeFunction(TextStream &s, const AbstractMetaType s << "template<> inline PyTypeObject *SbkType< ::" << metaType.cppSignature() << " >() " << "{ return " << cpythonTypeNameExt(metaType) << "; }\n"; } + +void HeaderGenerator::writeModuleCodeSnips(TextStream &s, const CodeSnipList &codeSnips, + TypeSystem::CodeSnipPosition position, + TypeSystem::Language language) const +{ + if (!codeSnips.isEmpty()) { + try { + writeCodeSnips(s, codeSnips, position, language); + } catch (const std::exception &e) { + throw Exception(msgSnippetError("module header of "_L1 + moduleName(), e.what())); + } + } +} diff --git a/sources/shiboken6/generator/shiboken/headergenerator.h b/sources/shiboken6/generator/shiboken/headergenerator.h index 5b5f5a4a..e986ff40 100644 --- a/sources/shiboken6/generator/shiboken/headergenerator.h +++ b/sources/shiboken6/generator/shiboken/headergenerator.h @@ -6,6 +6,7 @@ #include "shibokengenerator.h" #include "include.h" +#include "modifications_typedefs.h" #include @@ -56,6 +57,9 @@ private: void writeWrapperClass(TextStream &s, const QString &wrapperName, const GeneratorContext &classContext) const; void writeInheritedWrapperClassDeclaration(TextStream &s, const GeneratorContext &classContext) const; + void writeModuleCodeSnips(TextStream &s, const CodeSnipList &codeSnips, + TypeSystem::CodeSnipPosition position, + TypeSystem::Language language) const; AbstractMetaClassCList m_alternateTemplateIndexes; }; diff --git a/sources/shiboken6/generator/shiboken/shibokengenerator.cpp b/sources/shiboken6/generator/shiboken/shibokengenerator.cpp index d4a9e003..3830540c 100644 --- a/sources/shiboken6/generator/shiboken/shibokengenerator.cpp +++ b/sources/shiboken6/generator/shiboken/shibokengenerator.cpp @@ -561,7 +561,7 @@ QString ShibokenGenerator::cpythonWrapperCPtr(const TypeEntryCPtr &type, { if (!type->isWrapperType()) return QString(); - return u"reinterpret_cast< ::"_s + type->qualifiedCppName() + return u"reinterpret_cast< "_s + getFullTypeName(type) + u" *>(Shiboken::Conversions::cppPointer("_s + cpythonTypeNameExt(type) + u", reinterpret_cast("_s + argName + u")))"_s; } @@ -1326,7 +1326,7 @@ void ShibokenGenerator::processClassCodeSnip(QString &code, const GeneratorConte code.replace(u"%TYPE"_s, className); code.replace(u"%CPPTYPE"_s, metaClass->name()); - processCodeSnip(code); + processCodeSnip(code, context.effectiveClassName()); } void ShibokenGenerator::processCodeSnip(QString &code) const @@ -1344,6 +1344,15 @@ void ShibokenGenerator::processCodeSnip(QString &code) const replaceTypeCheckTypeSystemVariable(code); } +void ShibokenGenerator::processCodeSnip(QString &code, const QString &context) const +{ + try { + processCodeSnip(code); + } catch (const std::exception &e) { + throw Exception(msgSnippetError(context, e.what())); + } +} + ShibokenGenerator::ArgumentVarReplacementList ShibokenGenerator::getArgumentReplacement(const AbstractMetaFunctionCPtr &func, bool usePyArgs, TypeSystem::Language language, @@ -1648,7 +1657,7 @@ void ShibokenGenerator::writeCodeSnips(TextStream &s, replaceTemplateVariables(code, func); - processCodeSnip(code); + processCodeSnip(code, func->classQualifiedSignature()); s << "// Begin code injection\n" << code << "// End of code injection\n\n"; } diff --git a/sources/shiboken6/generator/shiboken/shibokengenerator.h b/sources/shiboken6/generator/shiboken/shibokengenerator.h index 80e94b84..25eb7854 100644 --- a/sources/shiboken6/generator/shiboken/shibokengenerator.h +++ b/sources/shiboken6/generator/shiboken/shibokengenerator.h @@ -27,7 +27,7 @@ struct GeneratorClassInfoCacheEntry; struct IncludeGroup; struct ShibokenGeneratorOptions; -QT_FORWARD_DECLARE_CLASS(TextStream) +class TextStream; // Function to be used for implementing nb_bool struct BoolCastFunction @@ -145,6 +145,7 @@ protected: /// Replaces variables for the user's custom code at global or class level. void processCodeSnip(QString &code) const; + void processCodeSnip(QString &code, const QString &context) const; void processClassCodeSnip(QString &code, const GeneratorContext &context) const; /** diff --git a/sources/shiboken6/libshiboken/CMakeLists.txt b/sources/shiboken6/libshiboken/CMakeLists.txt index 05c779fd..4638ca6f 100644 --- a/sources/shiboken6/libshiboken/CMakeLists.txt +++ b/sources/shiboken6/libshiboken/CMakeLists.txt @@ -30,7 +30,7 @@ if(SHIBOKEN_IS_CROSS_BUILD) set(host_python_path "${QFP_PYTHON_HOST_PATH}") set(use_pyc_in_embedding FALSE) else() - set(host_python_path "${PYTHON_EXECUTABLE}") + set(host_python_path "${Python_EXECUTABLE}") if(PYTHON_LIMITED_API) set(use_pyc_in_embedding FALSE) else() diff --git a/sources/shiboken6/libshiboken/basewrapper.cpp b/sources/shiboken6/libshiboken/basewrapper.cpp index 828c54a8..70e1e2f4 100644 --- a/sources/shiboken6/libshiboken/basewrapper.cpp +++ b/sources/shiboken6/libshiboken/basewrapper.cpp @@ -412,7 +412,7 @@ void SbkObjectType_tp_dealloc(PyTypeObject *sbkType) auto pyObj = reinterpret_cast(sbkType); PyObject_GC_UnTrack(pyObj); -#ifndef Py_LIMITED_API +#if !defined(Py_LIMITED_API) && !defined(PYPY_VERSION) # if PY_VERSION_HEX >= 0x030A0000 Py_TRASHCAN_BEGIN(pyObj, 1); # else @@ -430,7 +430,7 @@ void SbkObjectType_tp_dealloc(PyTypeObject *sbkType) Shiboken::Conversions::deleteConverter(sotp->converter); PepType_SOTP_delete(sbkType); } -#ifndef Py_LIMITED_API +#if !defined(Py_LIMITED_API) && !defined(PYPY_VERSION) # if PY_VERSION_HEX >= 0x030A0000 Py_TRASHCAN_END; # else diff --git a/sources/shiboken6/libshiboken/helper.cpp b/sources/shiboken6/libshiboken/helper.cpp index d2203fb4..9c75c00d 100644 --- a/sources/shiboken6/libshiboken/helper.cpp +++ b/sources/shiboken6/libshiboken/helper.cpp @@ -584,7 +584,7 @@ const char *typeNameOf(const char *typeIdName) return result; } -#if !defined(Py_LIMITED_API) && PY_VERSION_HEX >= 0x030A0000 +#if !defined(Py_LIMITED_API) && PY_VERSION_HEX >= 0x030A0000 && !defined(PYPY_VERSION) static int _getPyVerbose() { PyConfig config; @@ -597,7 +597,7 @@ int pyVerbose() { #ifdef Py_LIMITED_API return Pep_GetVerboseFlag(); -#elif PY_VERSION_HEX >= 0x030A0000 +#elif PY_VERSION_HEX >= 0x030A0000 && !defined(PYPY_VERSION) static const int result = _getPyVerbose(); return result; #else diff --git a/sources/shiboken6/libshiboken/pep384impl.cpp b/sources/shiboken6/libshiboken/pep384impl.cpp index 0d298150..37e51921 100644 --- a/sources/shiboken6/libshiboken/pep384impl.cpp +++ b/sources/shiboken6/libshiboken/pep384impl.cpp @@ -512,6 +512,16 @@ int PepCode_Check(PyObject *o) #endif // Py_LIMITED_API +#if defined(Py_LIMITED_API) || defined(PYPY_VERSION) +PyObject *PepFunction_GetDefaults(PyObject *function) +{ + auto *ob_ret = PyObject_GetAttrString(function, "__defaults__"); + Py_XDECREF(ob_ret); // returns borrowed ref + return ob_ret != Py_None ? ob_ret : nullptr; +} + +#endif // defined(Py_LIMITED_API) || defined(PYPY_VERSION) + /***************************************************************************** * * Support for datetime.h diff --git a/sources/shiboken6/libshiboken/pep384impl.h b/sources/shiboken6/libshiboken/pep384impl.h index 5a315744..39a6f3d1 100644 --- a/sources/shiboken6/libshiboken/pep384impl.h +++ b/sources/shiboken6/libshiboken/pep384impl.h @@ -410,6 +410,14 @@ LIBSHIBOKEN_API int PepCode_Check(PyObject *o); # define PepCode_GET_FLAGS(o) ((o)->co_flags) # define PepCode_GET_ARGCOUNT(o) ((o)->co_argcount) # define PepCode_Check PyCode_Check + +# ifdef PYPY_VERSION + +LIBSHIBOKEN_API PyObject *PepFunction_GetDefaults(PyObject *function); + +# else +# define PepFunction_GetDefaults PyFunction_GetDefaults +# endif #endif /***************************************************************************** diff --git a/sources/shiboken6/libshiboken/sbkversion.h.in b/sources/shiboken6/libshiboken/sbkversion.h.in index 7f99abc3..5c0b38fd 100644 --- a/sources/shiboken6/libshiboken/sbkversion.h.in +++ b/sources/shiboken6/libshiboken/sbkversion.h.in @@ -10,8 +10,8 @@ #define SHIBOKEN_MICRO_VERSION @shiboken_MICRO_VERSION@ #define SHIBOKEN_RELEASE_LEVEL "final" #define SHIBOKEN_SERIAL 0 -#define PYTHON_VERSION_MAJOR @PYTHON_VERSION_MAJOR@ -#define PYTHON_VERSION_MINOR @PYTHON_VERSION_MINOR@ -#define PYTHON_VERSION_PATCH @PYTHON_VERSION_PATCH@ +#define PYTHON_VERSION_MAJOR @Python_VERSION_MAJOR@ +#define PYTHON_VERSION_MINOR @Python_VERSION_MINOR@ +#define PYTHON_VERSION_PATCH @Python_VERSION_PATCH@ #endif diff --git a/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/mapping.py b/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/mapping.py index 4588b591..1af82e31 100644 --- a/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/mapping.py +++ b/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/mapping.py @@ -1,6 +1,8 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +# flake8: noqa E:203 + """ mapping.py @@ -20,10 +22,12 @@ from pathlib import Path from typing import TypeVar, Generic from _imp import is_builtin + class ellipsis(object): def __repr__(self): return "..." + ellipsis = ellipsis() Point = typing.Tuple[int, int] Variant = typing.Any @@ -38,7 +42,7 @@ _S = TypeVar("_S") MultiMap = typing.DefaultDict[str, typing.List[str]] # ulong_max is only 32 bit on windows. -ulong_max = 2*sys.maxsize+1 if len(struct.pack("L", 1)) != 4 else 0xffffffff +ulong_max = 2 * sys.maxsize + 1 if len(struct.pack("L", 1)) != 4 else 0xffffffff ushort_max = 0xffff GL_COLOR_BUFFER_BIT = 0x00004000 @@ -74,6 +78,7 @@ class _NotCalled(str): text = self if self.endswith(")") else self + "()" return eval(text, namespace) + USE_PEP563 = False # Note: we cannot know if this feature has been imported. # Otherwise it would be "sys.version_info[:2] >= (3, 7)". @@ -86,6 +91,7 @@ USE_PEP563 = False class Virtual(_NotCalled): pass + # Other types I simply could not find. class Missing(_NotCalled): # The string must be quoted, because the object does not exist. @@ -98,6 +104,7 @@ class Missing(_NotCalled): class Invalid(_NotCalled): pass + # Helper types class Default(_NotCalled): pass @@ -106,6 +113,7 @@ class Default(_NotCalled): class Instance(_NotCalled): pass + # Parameterized primitive variables class _Parameterized(object): def __init__(self, type): @@ -115,15 +123,18 @@ class _Parameterized(object): def __repr__(self): return f"{type(self).__name__}({self.type.__name__})" + # Mark the primitive variables to be moved into the result. class ResultVariable(_Parameterized): pass + # Mark the primitive variables to become Sequence, Iterable or List # (decided in the parser). class ArrayLikeVariable(_Parameterized): pass + StringList = ArrayLikeVariable(str) @@ -180,6 +191,7 @@ def check_module(mod): mod_name = mod.__name__ raise ImportError(f"Module '{mod_name}' is not a binary module!") + update_mapping = Reloader().update type_map = {} namespace = globals() # our module's __dict__ @@ -200,9 +212,9 @@ type_map.update({ "PyCallable": typing.Callable, "PyObject": object, "PyObject*": object, - "PyArrayObject": ArrayLikeVariable, # numpy + "PyArrayObject": ArrayLikeVariable, # numpy "PyPathLike": typing.Union[str, bytes, os.PathLike], - "PySequence": typing.Iterable, # important for numpy + "PySequence": typing.Iterable, # important for numpy "PyTypeObject": type, "QChar": str, "QHash": typing.Dict, @@ -266,16 +278,16 @@ type_map.update({ "ulong": int, "ULONG_MAX": ulong_max, "UINT64_MAX": 0xffffffff, - "unsigned char": int, # 5.9 + "unsigned char": int, # 5.9 "unsigned char*": str, "unsigned int": int, - "unsigned long int": int, # 5.6, RHEL 6.6 + "unsigned long int": int, # 5.6, RHEL 6.6 "unsigned long long": int, "unsigned long": int, - "unsigned short int": int, # 5.6, RHEL 6.6 + "unsigned short int": int, # 5.6, RHEL 6.6 "unsigned short": int, "ushort": int, - "void": int, # be more specific? + "void": int, # be more specific? "WId": WId, "zero(bytes)": b"", "zero(Char)": 0, @@ -289,7 +301,7 @@ type_map.update({ "numpy.ndarray": typing.List[typing.Any], "std.array[int, 4]": typing.List[int], "std.array[float, 4]": typing.List[float] - }) +}) type_map.update({ # Handling variables declared as array: @@ -301,8 +313,8 @@ type_map.update({ "array long long*" : ArrayLikeVariable(int), "array long*" : ArrayLikeVariable(int), "array short*" : ArrayLikeVariable(int), - "array signed char*" : bytes, - "array unsigned char*" : bytes, + "array signed char*" : typing.Union[bytes, bytearray, memoryview], + "array unsigned char*" : typing.Union[bytes, bytearray, memoryview], "array unsigned int*" : ArrayLikeVariable(int), "array unsigned short*" : ArrayLikeVariable(int), # PYSIDE-1646: New macOS primitive types @@ -313,17 +325,17 @@ type_map.update({ "array int32_t*" : ArrayLikeVariable(int), "array uint32_t*" : ArrayLikeVariable(int), "array intptr_t*" : ArrayLikeVariable(int), - }) +}) type_map.update({ # Special cases: - "char*" : bytes, - "QChar*" : bytes, + "char*" : typing.Union[bytes, bytearray, memoryview], + "QChar*" : typing.Union[bytes, bytearray, memoryview], "quint32*" : int, # only for QRandomGenerator "quint8*" : bytearray, # only for QCborStreamReader and QCborValue - "uchar*" : bytes, - "unsigned char*": bytes, - }) + "uchar*" : typing.Union[bytes, bytearray, memoryview], + "unsigned char*": typing.Union[bytes, bytearray, memoryview], +}) type_map.update({ # Handling variables that are returned, eventually as Tuples: @@ -345,7 +357,7 @@ type_map.update({ "uint*" : ResultVariable(int), "unsigned int*" : ResultVariable(int), "QStringList*" : ResultVariable(StringList), - }) +}) type_map.update({ @@ -353,20 +365,21 @@ type_map.update({ "[typing.Any]" : [typing.Any], "[typing.Any,typing.Any]" : [typing.Any, typing.Any], "None" : None, - }) +}) # PYSIDE-1328: We need to handle "self" explicitly. type_map.update({ "self" : "self", "cls" : "cls", - }) +}) # PYSIDE-1538: We need to treat "std::optional" accordingly. type_map.update({ "std.optional": typing.Optional, }) + # The Shiboken Part def init_Shiboken(): type_map.update({ @@ -376,6 +389,7 @@ def init_Shiboken(): }) return locals() + def init_minimal(): type_map.update({ "MinBool": bool, @@ -391,7 +405,7 @@ def init_sample(): "const char*": str, "Complex": complex, "double": float, - "ByteArray&": bytes, + "ByteArray&": typing.Union[bytes, bytearray, memoryview], "Foo.HANDLE": int, "HANDLE": int, "Null": None, @@ -399,7 +413,7 @@ def init_sample(): "OddBool": bool, "PStr": str, "PyDate": datetime.date, - "PyBuffer": bytes, + "PyBuffer": typing.Union[bytes, bytearray, memoryview], "sample.bool": bool, "sample.char": int, "sample.double": float, @@ -435,6 +449,7 @@ def init_smart(): # This missing type should be defined in module smart. We cannot set it to Missing() # because it is a container type. Therefore, we supply a surrogate: global SharedPtr + class SharedPtr(Generic[_S]): __module__ = "smart" smart.SharedPtr = SharedPtr @@ -448,7 +463,7 @@ def init_smart(): def init_PySide6_QtCore(): from PySide6.QtCore import Qt, QUrl, QDir, QKeyCombination from PySide6.QtCore import QRect, QRectF, QSize, QPoint, QLocale, QByteArray - from PySide6.QtCore import QMarginsF # 5.9 + from PySide6.QtCore import QMarginsF # 5.9 from PySide6.QtCore import SignalInstance try: # seems to be not generated by 5.9 ATM. @@ -459,23 +474,23 @@ def init_PySide6_QtCore(): "' '": " ", "'%'": "%", "'g'": "g", - "4294967295UL": 4294967295, # 5.6, RHEL 6.6 + "4294967295UL": 4294967295, # 5.6, RHEL 6.6 "CheckIndexOption.NoOption": Instance( - "PySide6.QtCore.QAbstractItemModel.CheckIndexOptions.NoOption"), # 5.11 + "PySide6.QtCore.QAbstractItemModel.CheckIndexOptions.NoOption"), # 5.11 "DescriptorType(-1)": int, # Native handle of QSocketDescriptor "false": False, "list of QAbstractAnimation": typing.List[PySide6.QtCore.QAbstractAnimation], "long long": int, "size_t": int, - "NULL": None, # 5.6, MSVC - "nullptr": None, # 5.9 - "PyBuffer": bytes, + "NULL": None, # 5.6, MSVC + "nullptr": None, # 5.9 + "PyBuffer": typing.Union[bytes, bytearray, memoryview], "PyByteArray": bytearray, - "PyBytes": bytes, + "PyBytes": typing.Union[bytes, bytearray, memoryview], "PyTuple": typing.Tuple, "QDeadlineTimer(QDeadlineTimer.Forever)": Instance("PySide6.QtCore.QDeadlineTimer"), "PySide6.QtCore.QUrl.ComponentFormattingOptions": - PySide6.QtCore.QUrl.ComponentFormattingOption, # mismatch option/enum, why??? + PySide6.QtCore.QUrl.ComponentFormattingOption, # mismatch option/enum, why??? "PyUnicode": typing.Text, "QByteArrayView": QByteArray, "Q_NULLPTR": None, @@ -485,15 +500,15 @@ def init_PySide6_QtCore(): "QDir.Filters(QDir.AllEntries | QDir.NoDotAndDotDot)"), "QDir.SortFlags(Name | IgnoreCase)": Instance( "QDir.SortFlags(QDir.Name | QDir.IgnoreCase)"), - "QEvent.Type.None" : None, - "QGenericArgument((0))": ellipsis, # 5.6, RHEL 6.6. Is that ok? + "QEvent.Type.None": None, + "QGenericArgument((0))": ellipsis, # 5.6, RHEL 6.6. Is that ok? "QGenericArgument()": ellipsis, "QGenericArgument(0)": ellipsis, - "QGenericArgument(NULL)": ellipsis, # 5.6, MSVC - "QGenericArgument(nullptr)": ellipsis, # 5.10 + "QGenericArgument(NULL)": ellipsis, # 5.6, MSVC + "QGenericArgument(nullptr)": ellipsis, # 5.10 "QGenericArgument(Q_NULLPTR)": ellipsis, "QJsonObject": typing.Dict[str, PySide6.QtCore.QJsonValue], - "QModelIndex()": Invalid("PySide6.QtCore.QModelIndex"), # repr is btw. very wrong, fix it?! + "QModelIndex()": Invalid("PySide6.QtCore.QModelIndex"), # repr is btw. very wrong, fix it?! "QModelIndexList": typing.List[PySide6.QtCore.QModelIndex], "PySideSignalInstance": SignalInstance, "QString()": "", @@ -501,17 +516,16 @@ def init_PySide6_QtCore(): "QStringList()": [], "QStringRef": str, "QStringRef": str, - "Qt.HANDLE": int, # be more explicit with some constants? + "Qt.HANDLE": int, # be more explicit with some constants? "QUrl.FormattingOptions(PrettyDecoded)": Instance( "QUrl.FormattingOptions(QUrl.PrettyDecoded)"), "QVariant()": Invalid(Variant), - "QVariant.Type": type, # not so sure here... - "QVariantMap": typing.Dict[str, Variant], + "QVariant.Type": type, # not so sure here... "QVariantMap": typing.Dict[str, Variant], }) try: type_map.update({ - "PySide6.QtCore.QMetaObject.Connection": PySide6.QtCore.Connection, # wrong! + "PySide6.QtCore.QMetaObject.Connection": PySide6.QtCore.Connection, # wrong! }) except AttributeError: # this does not exist on 5.9 ATM. @@ -536,7 +550,7 @@ def init_PySide6_QtConcurrent(): def init_PySide6_QtGui(): - from PySide6.QtGui import QPageLayout, QPageSize # 5.12 macOS + from PySide6.QtGui import QPageLayout, QPageSize # 5.12 macOS type_map.update({ "0.0f": 0.0, "1.0f": 1.0, @@ -546,9 +560,9 @@ def init_PySide6_QtGui(): "HBITMAP": int, "HICON": int, "HRGN": int, - "QPixmap()": Default("PySide6.QtGui.QPixmap"), # can't create without qApp - "QPlatformSurface*": int, # a handle - "QVector< QTextLayout.FormatRange >()": [], # do we need more structure? + "QPixmap()": Default("PySide6.QtGui.QPixmap"), # can't create without qApp + "QPlatformSurface*": int, # a handle + "QVector< QTextLayout.FormatRange >()": [], # do we need more structure? "uint32_t": int, "uint8_t": int, "USHRT_MAX": ushort_max, @@ -562,8 +576,9 @@ def init_PySide6_QtGui(): def init_PySide6_QtWidgets(): - from PySide6.QtWidgets import QWidget, QMessageBox, QStyleOption, QStyleHintReturn, QStyleOptionComplex - from PySide6.QtWidgets import QGraphicsItem, QStyleOptionGraphicsItem # 5.9 + from PySide6.QtWidgets import (QWidget, QMessageBox, QStyleOption, + QStyleHintReturn, QStyleOptionComplex, + QGraphicsItem, QStyleOptionGraphicsItem) type_map.update({ "QMessageBox.StandardButtons(Yes | No)": Instance( "QMessageBox.StandardButtons(QMessageBox.Yes | QMessageBox.No)"), @@ -584,7 +599,7 @@ def init_PySide6_QtSql(): from PySide6.QtSql import QSqlDatabase type_map.update({ "QLatin1StringView(QSqlDatabase.defaultConnection)": QSqlDatabase.defaultConnection, - "QVariant.Invalid": Invalid("Variant"), # not sure what I should create, here... + "QVariant.Invalid": Invalid("Variant"), # not sure what I should create, here... }) return locals() @@ -608,7 +623,7 @@ def init_PySide6_QtOpenGL(): type_map.update({ "GLbitfield": int, "GLenum": int, - "GLfloat": float, # 5.6, MSVC 15 + "GLfloat": float, # 5.6, MSVC 15 "GLint": int, "GLuint": int, }) diff --git a/sources/shiboken6/tests/CMakeLists.txt b/sources/shiboken6/tests/CMakeLists.txt index f3b09658..05f6e9e6 100644 --- a/sources/shiboken6/tests/CMakeLists.txt +++ b/sources/shiboken6/tests/CMakeLists.txt @@ -46,13 +46,18 @@ list(SORT TEST_FILES) set(test_blacklist "") if(SHIBOKEN_IS_CROSS_BUILD) - # PYTHON_EXECUTABLE will be empty when cross-building. + # Python_EXECUTABLE will be empty when cross-building. message(WARNING "Running tests when cross-compiling is not supported because it would require running " "a target python interpreter which might have a different architecture than the host." ) else() - find_package(PythonInterp REQUIRED) + find_package( + Python + ${USE_PYTHON_VERSION} + REQUIRED + COMPONENTS Interpreter Development + ) endif() if(NOT CTEST_TESTING_TIMEOUT) @@ -66,7 +71,7 @@ foreach(test_file ${TEST_FILES}) string(REGEX MATCH "/([^/]+)(binding|module)/([^/]+)_test.py" tmp ${test_file}) set(test_name "${CMAKE_MATCH_1}_${CMAKE_MATCH_3}") list(FIND test_blacklist ${test_name} expect_fail) - add_test(${test_name} ${PYTHON_EXECUTABLE} ${test_file}) + add_test(${test_name} ${Python_EXECUTABLE} ${test_file}) set_tests_properties(${test_name} PROPERTIES ENVIRONMENT "BUILD_DIR=${BUILD_DIR}") set_tests_properties(${test_name} PROPERTIES TIMEOUT ${CTEST_TESTING_TIMEOUT}) if (${expect_fail} GREATER -1) diff --git a/sources/shiboken6/tests/shibokenmodule/module_test.py b/sources/shiboken6/tests/shibokenmodule/module_test.py index 9b9a5ad9..a1f95575 100644 --- a/sources/shiboken6/tests/shibokenmodule/module_test.py +++ b/sources/shiboken6/tests/shibokenmodule/module_test.py @@ -7,17 +7,19 @@ import unittest from pathlib import Path sys.path.append(os.fspath(Path(__file__).resolve().parents[1])) -from shiboken_paths import init_paths +from shiboken_paths import init_paths # noqa: E402 init_paths() -from shiboken6 import Shiboken -from sample import * +from shiboken6 import Shiboken # noqa: E402 +from sample import BlackBox, ObjectType, ObjectModel, ObjectView, Point # noqa: E402 + class MultipleInherited (ObjectType, Point): def __init__(self): ObjectType.__init__(self) Point.__init__(self) + class TestShiboken(unittest.TestCase): def testIsValid(self): self.assertTrue(Shiboken.isValid(object())) @@ -55,7 +57,7 @@ class TestShiboken(unittest.TestCase): p = ObjectType() obj = ObjectType(p) obj2 = ObjectType(obj) - obj3 = ObjectType(obj) + obj3 = ObjectType(obj) # noqa: F841 self.assertEqual(Shiboken.dump(None), "Ordinary Python type.") Shiboken.dump(obj) @@ -69,9 +71,9 @@ class TestShiboken(unittest.TestCase): # Don't crash even after deleting an object Shiboken.invalidate(obj) - Shiboken.dump(obj) # deleted - Shiboken.dump(p) # child deleted - Shiboken.dump(obj2) # parent deleted + Shiboken.dump(obj) # deleted + Shiboken.dump(p) # child deleted + Shiboken.dump(obj2) # parent deleted def testDelete(self): obj = ObjectType() diff --git a/testing/buildlog.py b/testing/buildlog.py index d1ec7b14..f82191f9 100644 --- a/testing/buildlog.py +++ b/testing/buildlog.py @@ -77,6 +77,7 @@ class BuildLog(object): # we take the latest build for now. build_history.sort() self.history = build_history + self.python_version = None self._buildno = None if not is_ci: # there seems to be a timing problem in RHel 7.6, so we better don't touch it @@ -137,6 +138,9 @@ class BuildLog(object): path = self.selected.build_dir base = os.path.basename(path) res.extend(base.split("-")) + # add exact Python version + if self.python_version: + res.append("py" + ".".join(map(str, self.python_version))) # add all the python and qt subkeys for entry in res: parts = entry.split(".") @@ -149,5 +153,8 @@ class BuildLog(object): res.append(platform.processor()) return res + def set_python_version(self, version_triple): + self.python_version = version_triple + builds = BuildLog() diff --git a/testing/command.py b/testing/command.py index c73f269f..31a48f87 100644 --- a/testing/command.py +++ b/testing/command.py @@ -79,6 +79,8 @@ def test_project(project, args, blacklist, runs): for idx in range(runs): index = idx + 1 runner = TestRunner(builds.selected, project, index) + # For the full Python version we need to ask the TestRunner. + builds.set_python_version(runner.get_python_version()) print() print(f"********* Start testing of {project} *********") print("Config: Using", " ".join(builds.classifiers)) diff --git a/testing/runner.py b/testing/runner.py index 0d3b4c89..b52ac493 100644 --- a/testing/runner.py +++ b/testing/runner.py @@ -36,6 +36,31 @@ class TestRunner(object): self._setup_clang() self._setup() + def get_python_version(self): + """ + Finding the exact Python version. + --------------------------------- + + This is done by asking the interpreter, because it cannot reliably + be found from any file name parsing as a triple. + + Note: We need to look into the CMakeCache.txt file to find out + what CMake has found as the Python interpreter to use. + This is *not* necessarily the same Python that runs this script, + otherwise we could use the version info directly. + """ + look_python = os.path.join(self.test_dir, "CMakeCache.txt") + look_for = "PYTHON_EXECUTABLE:FILEPATH=" + with open(look_python) as f: + for line in f: + if line.startswith(look_for): + python_exec = line.split("=")[-1].strip() + res = subprocess.run([python_exec, "-c", + "import sys;print(sys.version_info[:3])"], + capture_output=True) + return eval(res.stdout.decode("utf-8")) + return None + def _setup_clang(self): if sys.platform != "win32": return diff --git a/testing/wheel_tester.py b/testing/wheel_tester.py index 70c43313..24bc890b 100644 --- a/testing/wheel_tester.py +++ b/testing/wheel_tester.py @@ -35,9 +35,9 @@ this_dir = os.path.dirname(this_file) setup_script_dir = os.path.abspath(os.path.join(this_dir, "..")) sys.path.append(setup_script_dir) -from build_scripts.utils import (find_files_using_glob, find_glob_in_path, +from build_scripts.utils import (find_files_using_glob, find_glob_in_path, # noqa: E402 remove_tree, run_process, run_process_output) -from build_scripts.log import log +from build_scripts.log import log # noqa: E402 log.setLevel(logging.DEBUG) @@ -83,7 +83,8 @@ def package_prefix_names(): # Note: shiboken6_generator is not needed for compile_using_nuitka, # but building modules with cmake needs it. if NEW_WHEELS: - return ["shiboken6", "shiboken6_generator", "PySide6_Essentials", "PySide6_Addons", "PySide6"] + return ["shiboken6", "shiboken6_generator", "PySide6_Essentials", "PySide6_Addons", + "PySide6"] else: return ["shiboken6", "shiboken6_generator", "PySide6"] @@ -360,7 +361,7 @@ def try_build_examples(): for modname in modules: # PYSIDE-1735: pyi files are no longer compatible with Python. # XXX Maybe add a test with Mypy here? - pass # execute_script(src_path / f"{modname}.pyi") + pass # execute_script(src_path / f"{modname}.pyi") def run_wheel_tests(install_wheels, wheels_dir_name): @@ -392,7 +393,8 @@ if __name__ == "__main__": ) parser.add_argument("--qmake", type=str, help="Path to qmake") parser.add_argument("--cmake", type=str, help="Path to cmake") - parser.add_argument("--wheels-dir", type=str, help="Path to where the wheels are", default="dist") + parser.add_argument("--wheels-dir", type=str, help="Path to where the wheels are", + default="dist") parser.add_argument("--new", action="store_true", help="Option to test new wheels") options = parser.parse_args() QMAKE_PATH = find_executable("qmake", options.qmake) diff --git a/tools/cross_compile_android/android_utilities.py b/tools/cross_compile_android/android_utilities.py index e9363117..b09ab7bd 100644 --- a/tools/cross_compile_android/android_utilities.py +++ b/tools/cross_compile_android/android_utilities.py @@ -126,14 +126,14 @@ def download_android_ndk(ndk_path: Path): print(f"NDK path found in {str(ndk_version_path)}") else: ndk_path.mkdir(parents=True, exist_ok=True) - url = f"https://dl.google.com/android/repository/android-ndk-r{ANDROID_NDK_VERSION}-linux.zip" + url = (f"https://dl.google.com/android/repository" + f"/android-ndk-r{ANDROID_NDK_VERSION}-linux.zip") print(f"Downloading Android Ndk version r{ANDROID_NDK_VERSION}") _download(url=url, destination=ndk_zip_path) print("Unpacking Android Ndk") - _unpack(zip_file=(ndk_path / - f"android-ndk-r{ANDROID_NDK_VERSION}-linux.zip"), + _unpack(zip_file=(ndk_path / f"android-ndk-r{ANDROID_NDK_VERSION}-linux.zip"), destination=ndk_path) return ndk_version_path diff --git a/tools/cross_compile_android/main.py b/tools/cross_compile_android/main.py index 97f4030a..9c4616e9 100644 --- a/tools/cross_compile_android/main.py +++ b/tools/cross_compile_android/main.py @@ -4,12 +4,10 @@ import sys import logging import argparse -import tempfile -import subprocess import stat import warnings +import shutil from dataclasses import dataclass -from typing import List from pathlib import Path from git import Repo, RemoteProgress @@ -17,22 +15,11 @@ from tqdm import tqdm from jinja2 import Environment, FileSystemLoader from android_utilities import (run_command, download_android_commandlinetools, - download_android_ndk, install_android_packages) + download_android_ndk, install_android_packages) # Note: Does not work with PyEnv. Your Host Python should contain openssl. PYTHON_VERSION = "3.10" -APIC_HELP = (''' -Points to the installation path of Python for the specific Android -platform. If the path given does not exist, then Python for Android -is cross compiled for the specific platform and installed into this -path as /Python-'plat_name'/_install. - -If this path is not given, then Python for Android is cross-compiled -into a temportary directory, which is deleted when the Qt for Python -Android wheels are created. -''') - SKIP_UPDATE_HELP = ("skip the updation of SDK packages build-tools, platform-tools to" " latest version") @@ -41,6 +28,21 @@ Accepts license automatically for Android SDK installation. Otherwise, accept the license manually through command line. ''') +CLEAN_CACHE_HELP = (''' +Cleans cache stored in $HOME/.pyside6_deploy_cache. +Options: + +1. all - all the cache including Android Ndk, Android Sdk and Cross-compiled Python are deleted. +2. ndk - Only the Android Ndk is deleted. +3. sdk - Only the Android Sdk is deleted. +4. python - The cross compiled Python for all platforms, the cloned CPython, the cross compilation + scripts for all platforms are deleted. +5. toolchain - The CMake toolchain file required for cross-compiling Qt for Python, for all + platforms are deleted. + +If --clean-cache is used and no explicit value is suppied, then `all` is used as default. +''') + @dataclass class PlatformData: @@ -77,14 +79,15 @@ if __name__ == "__main__": formatter_class=argparse.RawTextHelpFormatter, ) - parser.add_argument("-p", "--plat-name", type=str, required=True, + parser.add_argument("-p", "--plat-name", type=str, nargs="*", choices=["aarch64", "armv7a", "i686", "x86_64"], - help="Android target platform name") + default=["aarch64", "armv7a", "i686", "x86_64"], dest="plat_names", + help="Android target platforms") parser.add_argument("-v", "--verbose", help="run in verbose mode", action="store_const", dest="loglevel", const=logging.INFO) parser.add_argument("--api-level", type=str, default="31", help="Android API level to use") - parser.add_argument("--ndk-path", type=str, help="Path to Android NDK (Preferred 25b)") + parser.add_argument("--ndk-path", type=str, help="Path to Android NDK (Preferred r25c)") # sdk path is needed to compile all the Qt Java Acitivity files into Qt6AndroidBindings.jar parser.add_argument("--sdk-path", type=str, help="Path to Android SDK") parser.add_argument("--qt-install-path", type=str, required=not occp_exists(), @@ -93,10 +96,6 @@ if __name__ == "__main__": parser.add_argument("-occp", "--only-cross-compile-python", action="store_true", help="Only cross compiles Python for the specified Android platform") - parser.add_argument("-apic", "--android-python-install-path", type=str, default=None, - required=occp_exists(), - help=APIC_HELP) - parser.add_argument("--dry-run", action="store_true", help="show the commands to be run") parser.add_argument("--skip-update", action="store_true", @@ -105,6 +104,10 @@ if __name__ == "__main__": parser.add_argument("--auto-accept-license", action="store_true", help=ACCEPT_LICENSE_HELP) + parser.add_argument("--clean-cache", type=str, nargs="?", const="all", + choices=["all", "python", "ndk", "sdk", "toolchain"], + help=CLEAN_CACHE_HELP) + args = parser.parse_args() logging.basicConfig(level=args.loglevel) @@ -113,21 +116,43 @@ if __name__ == "__main__": ndk_path = args.ndk_path sdk_path = args.sdk_path only_py_cross_compile = args.only_cross_compile_python - python_path = args.android_python_install_path - # the same android platforms are named differently in CMake, Cpython and Qt. - # Hence, we need to distinguish them - qt_plat_name = None android_abi = None gcc_march = None plat_bits = None dry_run = args.dry_run - plat_name = args.plat_name + plat_names = args.plat_names api_level = args.api_level skip_update = args.skip_update auto_accept_license = args.auto_accept_license + clean_cache = args.clean_cache # auto download Android NDK and SDK pyside6_deploy_cache = Path.home() / ".pyside6_android_deploy" + logging.info(f"Cache created at {str(pyside6_deploy_cache.resolve())}") + pyside6_deploy_cache.mkdir(exist_ok=True) + + if pyside6_deploy_cache.exists() and clean_cache: + if clean_cache == "all": + shutil.rmtree(pyside6_deploy_cache) + elif clean_cache == "ndk": + cached_ndk_dir = pyside6_deploy_cache / "android-ndk" + if cached_ndk_dir.exists(): + shutil.rmtree(cached_ndk_dir) + elif clean_cache == "sdk": + cached_sdk_dir = pyside6_deploy_cache / "android-sdk" + if cached_sdk_dir.exists(): + shutil.rmtree(cached_sdk_dir) + elif clean_cache == "python": + cached_cpython_dir = pyside6_deploy_cache / "cpython" + if cached_cpython_dir.exists(): + shutil.rmtree(pyside6_deploy_cache / "cpython") + for cc_python_path in pyside6_deploy_cache.glob("Python-*"): + if cc_python_path.is_dir(): + shutil.rmtree(cc_python_path) + elif clean_cache == "toolchain": + for toolchain_path in pyside6_deploy_cache.glob("toolchain_*"): + if toolchain_path.is_file(): + toolchain_path.unlink() if not ndk_path: # Download android ndk @@ -140,80 +165,79 @@ if __name__ == "__main__": install_android_packages(android_sdk_dir=sdk_path, android_api=api_level, dry_run=dry_run, accept_license=auto_accept_license, skip_update=skip_update) - # python path is valid, if Python for android installation exists in python_path - valid_python_path = True - if python_path and Path(python_path).exists(): - expected_dirs = ["lib", "include"] - for expected_dir in expected_dirs: - if not (Path(python_path) / expected_dir).is_dir(): - valid_python_path = False - warnings.warn( - "Given target Python, given through --android-python-install-path does not" - "contain Python. New Python for android will be cross compiled and installed" - "in this directory" - ) - break - templates_path = Path(__file__).parent / "templates" - # for armv7a the API level dependent binaries like clang are named - # armv7a-linux-androideabi27-clang, as opposed to other platforms which - # are named like x86_64-linux-android27-clang - platform_data = None - if plat_name == "armv7a": - platform_data = PlatformData("armv7a", f"eabi{api_level}", "armeabi-v7a", "armv7", "armv7", - "32") - elif plat_name == "aarch64": - platform_data = PlatformData("aarch64", api_level, "arm64-v8a", "arm64_v8a", "armv8-a", "64") - elif plat_name == "i686": - platform_data = PlatformData("i686", api_level, "x86", "x86", "i686", "32") - else: # plat_name is x86_64 - platform_data = PlatformData("x86_64", api_level, "x86_64", "x86_64", "x86-64", "64") - - # clone cpython and checkout 3.10 - with tempfile.TemporaryDirectory() as temp_dir: + for plat_name in plat_names: + # for armv7a the API level dependent binaries like clang are named + # armv7a-linux-androideabi27-clang, as opposed to other platforms which + # are named like x86_64-linux-android27-clang + platform_data = None + if plat_name == "armv7a": + platform_data = PlatformData("armv7a", api_level, "armeabi-v7a", "armv7", + "armv7", "32") + elif plat_name == "aarch64": + platform_data = PlatformData("aarch64", api_level, "arm64-v8a", "arm64_v8a", "armv8-a", + "64") + elif plat_name == "i686": + platform_data = PlatformData("i686", api_level, "x86", "x86", "i686", "32") + else: # plat_name is x86_64 + platform_data = PlatformData("x86_64", api_level, "x86_64", "x86_64", "x86-64", "64") + + # python path is valid, if Python for android installation exists in python_path + python_path = (pyside6_deploy_cache / f"Python-{platform_data.plat_name}-linux-android" + / "_install") + valid_python_path = python_path.exists() + if Path(python_path).exists(): + expected_dirs = ["lib", "include"] + for expected_dir in expected_dirs: + if not (Path(python_path) / expected_dir).is_dir(): + valid_python_path = False + warnings.warn( + f"{str(python_path.resolve())} is corrupted. New Python for {plat_name} " + f"android will be cross-compiled into {str(pyside6_deploy_cache.resolve())}" + ) + break + environment = Environment(loader=FileSystemLoader(templates_path)) - temp_dir = Path(temp_dir) - logging.info(f"temp dir created at {temp_dir}") - if not python_path or not valid_python_path: - cpython_dir = temp_dir / "cpython" - python_ccompile_script = cpython_dir / "cross_compile.sh" - - logging.info(f"cloning cpython {PYTHON_VERSION}") - Repo.clone_from( - "https://github.com/python/cpython.git", - cpython_dir, - progress=CloneProgress(), - branch=PYTHON_VERSION, - ) + if not valid_python_path: + # clone cpython and checkout 3.10 + cpython_dir = pyside6_deploy_cache / "cpython" + python_ccompile_script = cpython_dir / f"cross_compile_{plat_name}.sh" + + if not cpython_dir.exists(): + logging.info(f"cloning cpython {PYTHON_VERSION}") + Repo.clone_from( + "https://github.com/python/cpython.git", + cpython_dir, + progress=CloneProgress(), + branch=PYTHON_VERSION, + ) - if not python_path: - android_py_install_path_prefix = temp_dir - else: - android_py_install_path_prefix = python_path + if not python_ccompile_script.exists(): + # use jinja2 to create cross_compile.sh script + template = environment.get_template("cross_compile.tmpl.sh") + content = template.render( + plat_name=platform_data.plat_name, + ndk_path=ndk_path, + api_level=platform_data.api_level, + android_py_install_path_prefix=pyside6_deploy_cache, + ) - # use jinja2 to create cross_compile.sh script - template = environment.get_template("cross_compile.tmpl.sh") - content = template.render( - plat_name=platform_data.plat_name, - ndk_path=ndk_path, - api_level=platform_data.api_level, - android_py_install_path_prefix=android_py_install_path_prefix, - ) + logging.info(f"Writing Python cross compile script into {python_ccompile_script}") + with open(python_ccompile_script, mode="w", encoding="utf-8") as ccompile_script: + ccompile_script.write(content) - logging.info(f"Writing Python cross compile script into {python_ccompile_script}") - with open(python_ccompile_script, mode="w", encoding="utf-8") as ccompile_script: - ccompile_script.write(content) + # give run permission to cross compile script + python_ccompile_script.chmod(python_ccompile_script.stat().st_mode | stat.S_IEXEC) - # give run permission to cross compile script - python_ccompile_script.chmod(python_ccompile_script.stat().st_mode | stat.S_IEXEC) + # clean built files + logging.info("Cleaning CPython built files") + run_command(["make", "distclean"], cwd=cpython_dir, dry_run=dry_run, ignore_fail=True) # run the cross compile script logging.info(f"Running Python cross-compile for platform {platform_data.plat_name}") - run_command(["./cross_compile.sh"], cwd=cpython_dir, dry_run=dry_run, show_stdout=True) - - python_path = (f"{android_py_install_path_prefix}/Python-{platform_data.plat_name}-linux-android/" - "_install") + run_command([f"./{python_ccompile_script.name}"], cwd=cpython_dir, dry_run=dry_run, + show_stdout=True) # run patchelf to change the SONAME of libpython from libpython3.x.so.1.0 to # libpython3.x.so, to match with python_for_android's Python library. Otherwise, @@ -224,46 +248,52 @@ if __name__ == "__main__": logging.info( f"Cross compile Python for Android platform {platform_data.plat_name}. " - f"Final installation in " - f"{python_path}" + f"Final installation in {python_path}" ) if only_py_cross_compile: - sys.exit(0) - - qfp_toolchain = temp_dir / f"toolchain_{platform_data.plat_name}.cmake" - template = environment.get_template("toolchain_default.tmpl.cmake") - content = template.render( - ndk_path=ndk_path, - sdk_path=sdk_path, - api_level=platform_data.api_level, - qt_install_path=qt_install_path, - plat_name=platform_data.plat_name, - android_abi=platform_data.android_abi, - qt_plat_name=platform_data.qt_plat_name, - gcc_march=platform_data.gcc_march, - plat_bits=platform_data.plat_bits, - python_version=PYTHON_VERSION, - target_python_path=python_path - ) - - logging.info(f"Writing Qt for Python toolchain file into" - f"{qfp_toolchain}") - with open(qfp_toolchain, mode="w", encoding="utf-8") as ccompile_script: - ccompile_script.write(content) - - # give run permission to cross compile script - qfp_toolchain.chmod(qfp_toolchain.stat().st_mode | stat.S_IEXEC) + continue + + if only_py_cross_compile: + requested_platforms = ",".join(plat_names) + print(f"Python for Android platforms: {requested_platforms} cross compiled " + f"to {str(pyside6_deploy_cache)}") + sys.exit(0) + + qfp_toolchain = pyside6_deploy_cache / f"toolchain_{platform_data.plat_name}.cmake" + + if not qfp_toolchain.exists(): + template = environment.get_template("toolchain_default.tmpl.cmake") + content = template.render( + ndk_path=ndk_path, + sdk_path=sdk_path, + api_level=platform_data.api_level, + qt_install_path=qt_install_path, + plat_name=platform_data.plat_name, + android_abi=platform_data.android_abi, + qt_plat_name=platform_data.qt_plat_name, + gcc_march=platform_data.gcc_march, + plat_bits=platform_data.plat_bits, + python_version=PYTHON_VERSION, + target_python_path=python_path + ) + + logging.info(f"Writing Qt for Python toolchain file into {qfp_toolchain}") + with open(qfp_toolchain, mode="w", encoding="utf-8") as ccompile_script: + ccompile_script.write(content) + + # give run permission to cross compile script + qfp_toolchain.chmod(qfp_toolchain.stat().st_mode | stat.S_IEXEC) # run the cross compile script logging.info(f"Running Qt for Python cross-compile for platform {platform_data.plat_name}") qfp_ccompile_cmd = [sys.executable, "setup.py", "bdist_wheel", "--parallel=9", - "--ignore-git", "--standalone", "--limited-api=yes", + "--standalone", "--limited-api=yes", f"--cmake-toolchain-file={str(qfp_toolchain.resolve())}", f"--qt-host-path={qt_install_path}/gcc_64", f"--plat-name=android_{platform_data.plat_name}", f"--python-target-path={python_path}", (f"--qt-target-path={qt_install_path}/" - f"android_{platform_data.qt_plat_name}"), - "--no-qt-tools", "--skip-docs", "--unity"] + f"android_{platform_data.qt_plat_name}"), + "--no-qt-tools", "--unity"] run_command(qfp_ccompile_cmd, cwd=pyside_setup_dir, dry_run=dry_run, show_stdout=True) diff --git a/tools/cross_compile_android/templates/cross_compile.tmpl.sh b/tools/cross_compile_android/templates/cross_compile.tmpl.sh index ccf0cf8e..85d032e2 100644 --- a/tools/cross_compile_android/templates/cross_compile.tmpl.sh +++ b/tools/cross_compile_android/templates/cross_compile.tmpl.sh @@ -6,11 +6,17 @@ export HOST_ARCH={{ plat_name }}-linux-android export TOOLCHAIN={{ ndk_path }}/toolchains/llvm/prebuilt/linux-x86_64/bin export TOOL_PREFIX=$TOOLCHAIN/$HOST_ARCH export PLATFORM_API={{ api_level }} +{% if plat_name == "armv7a" -%} +export CXX=${TOOL_PREFIX}eabi${PLATFORM_API}-clang++ +export CPP="${TOOL_PREFIX}eabi${PLATFORM_API}-clang++ -E" +export CC=${TOOL_PREFIX}eabi${PLATFORM_API}-clang +{% else %} export CXX=${TOOL_PREFIX}${PLATFORM_API}-clang++ export CPP="${TOOL_PREFIX}${PLATFORM_API}-clang++ -E" +export CC=${TOOL_PREFIX}${PLATFORM_API}-clang +{% endif %} export AR=$TOOLCHAIN/llvm-ar export RANLIB=$TOOLCHAIN/llvm-ranlib -export CC=$TOOL_PREFIX${PLATFORM_API}-clang export LD=$TOOLCHAIN/ld export READELF=$TOOLCHAIN/llvm-readelf export CFLAGS='-fPIC -DANDROID' diff --git a/tools/cross_compile_android/templates/toolchain_default.tmpl.cmake b/tools/cross_compile_android/templates/toolchain_default.tmpl.cmake index 34338999..a691d50f 100644 --- a/tools/cross_compile_android/templates/toolchain_default.tmpl.cmake +++ b/tools/cross_compile_android/templates/toolchain_default.tmpl.cmake @@ -5,7 +5,11 @@ cmake_minimum_required(VERSION 3.18) include_guard(GLOBAL) set(CMAKE_SYSTEM_NAME Android) +{% if plat_name == "armv7a" -%} +set(CMAKE_SYSTEM_PROCESSOR armv7-a) +{% else %} set(CMAKE_SYSTEM_PROCESSOR {{ plat_name }}) +{% endif %} set(CMAKE_ANDROID_API {{ api_level }}) set(CMAKE_ANDROID_NDK {{ ndk_path }}) set(CMAKE_ANDROID_ARCH_ABI {{ android_abi }}) @@ -15,8 +19,12 @@ if(NOT DEFINED ANDROID_PLATFORM AND NOT DEFINED ANDROID_NATIVE_API_LEVEL) set(ANDROID_PLATFORM "android-{{ api_level }}" CACHE STRING "") endif() set(ANDROID_SDK_ROOT {{ sdk_path }}) - -set(QT_COMPILER_FLAGS "--target={{ plat_name }}-linux-android{{ api_level }} \ +{% if plat_name == "armv7a" -%} +set(_TARGET_NAME_ENDING "eabi{{ api_level }}") +{% else %} +set(_TARGET_NAME_ENDING "{{ api_level }}") +{% endif %} +set(QT_COMPILER_FLAGS "--target={{ plat_name }}-linux-android${_TARGET_NAME_ENDING} \ -fomit-frame-pointer \ -march={{ gcc_march }} \ -msse4.2 \ diff --git a/tools/doc_modules.py b/tools/doc_modules.py index 7d3982c6..d46f4db0 100644 --- a/tools/doc_modules.py +++ b/tools/doc_modules.py @@ -54,7 +54,7 @@ def required_typesystems(module): parser.setContentHandler(handler) parser.parse(typesystem_file) except Exception as e: - print(f"Error parsing {typesystem_file}: {e}", file=sys.stderr) + print(f"Warning: XML error parsing {typesystem_file}: {e}", file=sys.stderr) return handler.required_modules diff --git a/wheel_artifacts/pyproject.toml.base b/wheel_artifacts/pyproject.toml.base index 22da9c89..9e8546e8 100644 --- a/wheel_artifacts/pyproject.toml.base +++ b/wheel_artifacts/pyproject.toml.base @@ -13,6 +13,7 @@ dynamic = ["version"] requires-python = ">=3.8, <3.13" keywords = ["Qt"] license = {text = "LGPL"} +dependencies = PROJECT_DEPENDENCIES classifiers = [ "Development Status :: 5 - Production/Stable", "Environment :: Console", @@ -50,12 +51,11 @@ Repository = "https://code.qt.io/cgit/pyside/pyside-setup.git/" Changelog = "https://code.qt.io/cgit/pyside/pyside-setup.git/tree/doc/changelogs" Tracker = "https://bugreports.qt.io/projects/PYSIDE" +PROJECT_SCRIPTS + [tool.distutils.bdist_wheel] py_limited_api = "cp38" plat_name = PROJECT_TAG -[tool.setuptools.packages] -find = {} - [tool.setuptools.dynamic] version = {attr = PROJECT_VERSION} diff --git a/wheel_artifacts/setup.py.base b/wheel_artifacts/setup.py.base index e22298b9..d65f746e 100644 --- a/wheel_artifacts/setup.py.base +++ b/wheel_artifacts/setup.py.base @@ -20,9 +20,7 @@ class build_ext(Command): setup_args = dict( include_package_data=True, packages = ["{name}"], - entry_points = {console_scripts}, ext_modules = [Extension("{fake_ext}", [], py_limited_api=True)], - install_requires={install}, cmdclass=dict([("build_ext", build_ext)]), ) setup(**setup_args)